Merge pull request #10449 from umbraco/v9/task/package-refactor-startup-checks-PR1
Implements package migration startup checks
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Umbraco.Cms.Core.Logging;
|
||||
@@ -40,7 +40,7 @@ namespace Umbraco.Cms.Core.Composing
|
||||
/// file properties (false) or the file contents (true).</remarks>
|
||||
private string GetFileHash(IEnumerable<(FileSystemInfo fileOrFolder, bool scanFileContent)> filesAndFolders)
|
||||
{
|
||||
using (_logger.DebugDuration<TypeLoader>("Determining hash of code files on disk", "Hash determined"))
|
||||
using (_logger.DebugDuration<RuntimeHash>("Determining hash of code files on disk", "Hash determined"))
|
||||
{
|
||||
// get the distinct file infos to hash
|
||||
var uniqInfos = new HashSet<string>();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Umbraco.Cms.Core.Configuration.Models
|
||||
{
|
||||
@@ -24,6 +24,13 @@ namespace Umbraco.Cms.Core.Configuration.Models
|
||||
/// </summary>
|
||||
public bool UpgradeUnattended { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether unattended package migrations are enabled.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is true by default.
|
||||
/// </remarks>
|
||||
public bool PackageMigrationsUnattended { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value to use for creating a user with a name for Unattended Installs
|
||||
|
||||
@@ -7,6 +7,13 @@ namespace Umbraco.Cms.Core
|
||||
/// </summary>
|
||||
public static class Conventions
|
||||
{
|
||||
public static class Migrations
|
||||
{
|
||||
public const string UmbracoUpgradePlanName = "Umbraco.Core";
|
||||
public const string KeyValuePrefix = "Umbraco.Core.Upgrader.State+";
|
||||
public const string UmbracoUpgradePlanKey = KeyValuePrefix + UmbracoUpgradePlanName;
|
||||
}
|
||||
|
||||
public static class PermissionCategories
|
||||
{
|
||||
public const string ContentCategory = "content";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Umbraco.Cms.Core
|
||||
namespace Umbraco.Cms.Core
|
||||
{
|
||||
public static partial class Constants
|
||||
{
|
||||
@@ -59,8 +59,7 @@
|
||||
public const string RecycleBinMediaPathPrefix = "-1,-21,";
|
||||
|
||||
public const int DefaultLabelDataTypeId = -92;
|
||||
public const string UmbracoConnectionName = "umbracoDbDSN";
|
||||
public const string UmbracoUpgradePlanName = "Umbraco.Core";
|
||||
public const string UmbracoConnectionName = "umbracoDbDSN";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using Umbraco.Cms.Core.HealthChecks;
|
||||
using Umbraco.Cms.Core.HealthChecks.NotificationMethods;
|
||||
using Umbraco.Cms.Core.Manifest;
|
||||
using Umbraco.Cms.Core.Media.EmbedProviders;
|
||||
using Umbraco.Cms.Core.Packaging;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
@@ -32,7 +33,9 @@ namespace Umbraco.Cms.Core.DependencyInjection
|
||||
{
|
||||
builder.CacheRefreshers().Add(() => builder.TypeLoader.GetCacheRefreshers());
|
||||
builder.DataEditors().Add(() => builder.TypeLoader.GetDataEditors());
|
||||
builder.Actions().Add(() => builder.TypeLoader.GetTypes<IAction>());
|
||||
builder.Actions().Add(() => builder.TypeLoader.GetActions());
|
||||
builder.PackageMigrationPlans().Add(() => builder.TypeLoader.GetPackageMigrationPlans());
|
||||
|
||||
// register known content apps
|
||||
builder.ContentApps()
|
||||
.Append<ListViewContentAppFactory>()
|
||||
@@ -42,6 +45,7 @@ namespace Umbraco.Cms.Core.DependencyInjection
|
||||
.Append<ContentTypeListViewContentAppFactory>()
|
||||
.Append<ContentTypePermissionsContentAppFactory>()
|
||||
.Append<ContentTypeTemplatesContentAppFactory>();
|
||||
|
||||
// all built-in finders in the correct order,
|
||||
// devs can then modify this list on application startup
|
||||
builder.ContentFinders()
|
||||
@@ -116,6 +120,13 @@ namespace Umbraco.Cms.Core.DependencyInjection
|
||||
builder.BackOfficeAssets();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the package migration plans collection builder.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder.</param>
|
||||
public static PackageMigrationPlanCollectionBuilder PackageMigrationPlans(this IUmbracoBuilder builder)
|
||||
=> builder.WithCollectionBuilder<PackageMigrationPlanCollectionBuilder>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the actions collection builder.
|
||||
/// </summary>
|
||||
|
||||
@@ -5,9 +5,26 @@ namespace Umbraco.Extensions
|
||||
{
|
||||
public static class RuntimeStateExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns true if the installer is enabled based on the current runtime state
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
/// <returns></returns>
|
||||
public static bool EnableInstaller(this IRuntimeState state)
|
||||
=> state.Level == RuntimeLevel.Install || state.Level == RuntimeLevel.Upgrade || state.Level == RuntimeLevel.PackageMigrations;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if Umbraco <see cref="IRuntimeState"/> is greater than <see cref="RuntimeLevel.BootFailed"/>
|
||||
/// </summary>
|
||||
public static bool UmbracoCanBoot(this IRuntimeState state) => state.Level > RuntimeLevel.BootFailed;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the runtime state indicates that unattended boot logic should execute
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
/// <returns></returns>
|
||||
public static bool RunUnattendedBootLogic(this IRuntimeState state)
|
||||
=> (state.Reason == RuntimeLevelReason.UpgradeMigrations || state.Reason == RuntimeLevelReason.UpgradePackageMigrations)
|
||||
&& state.Level == RuntimeLevel.Run;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Cms.Core.Actions;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
using Umbraco.Cms.Core.Packaging;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
@@ -12,19 +14,27 @@ namespace Umbraco.Extensions
|
||||
public static class TypeLoaderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all classes implementing <see cref="IDataEditor"/>.
|
||||
/// Gets all types implementing <see cref="IDataEditor"/>.
|
||||
/// </summary>
|
||||
public static IEnumerable<Type> GetDataEditors(this TypeLoader mgr)
|
||||
{
|
||||
return mgr.GetTypes<IDataEditor>();
|
||||
}
|
||||
public static IEnumerable<Type> GetDataEditors(this TypeLoader mgr) => mgr.GetTypes<IDataEditor>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets all classes implementing ICacheRefresher.
|
||||
/// Gets all types implementing ICacheRefresher.
|
||||
/// </summary>
|
||||
public static IEnumerable<Type> GetCacheRefreshers(this TypeLoader mgr)
|
||||
{
|
||||
return mgr.GetTypes<ICacheRefresher>();
|
||||
}
|
||||
public static IEnumerable<Type> GetCacheRefreshers(this TypeLoader mgr) => mgr.GetTypes<ICacheRefresher>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets all types implementing <see cref="PackageMigrationPlan"/>
|
||||
/// </summary>
|
||||
/// <param name="mgr"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Type> GetPackageMigrationPlans(this TypeLoader mgr) => mgr.GetTypes<PackageMigrationPlan>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets all types implementing <see cref="IAction"/>
|
||||
/// </summary>
|
||||
/// <param name="mgr"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Type> GetActions(this TypeLoader mgr) => mgr.GetTypes<IAction>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,14 @@ namespace Umbraco.Cms.Core.Migrations
|
||||
public MigrationPlan(string name)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name));
|
||||
}
|
||||
|
||||
Name = name;
|
||||
}
|
||||
@@ -48,7 +53,7 @@ namespace Umbraco.Cms.Core.Migrations
|
||||
private MigrationPlan Add(string sourceState, string targetState, Type migration)
|
||||
{
|
||||
if (sourceState == null)
|
||||
throw new ArgumentNullException(nameof(sourceState));
|
||||
throw new ArgumentNullException(nameof(sourceState), $"{nameof(sourceState)} is null, {nameof(MigrationPlan)}.{nameof(MigrationPlan.From)} must not have been called.");
|
||||
if (targetState == null)
|
||||
throw new ArgumentNullException(nameof(targetState));
|
||||
if (string.IsNullOrWhiteSpace(targetState))
|
||||
@@ -90,6 +95,9 @@ namespace Umbraco.Cms.Core.Migrations
|
||||
public MigrationPlan To(string targetState)
|
||||
=> To<NoopMigration>(targetState);
|
||||
|
||||
|
||||
public MigrationPlan To(Guid targetState)
|
||||
=> To<NoopMigration>(targetState.ToString());
|
||||
/// <summary>
|
||||
/// Adds a transition to a target state through a migration.
|
||||
/// </summary>
|
||||
@@ -97,12 +105,19 @@ namespace Umbraco.Cms.Core.Migrations
|
||||
where TMigration : IMigration
|
||||
=> To(targetState, typeof(TMigration));
|
||||
|
||||
public MigrationPlan To<TMigration>(Guid targetState)
|
||||
where TMigration : IMigration
|
||||
=> To(targetState, typeof(TMigration));
|
||||
|
||||
/// <summary>
|
||||
/// Adds a transition to a target state through a migration.
|
||||
/// </summary>
|
||||
public MigrationPlan To(string targetState, Type migration)
|
||||
=> Add(_prevState, targetState, migration);
|
||||
|
||||
public MigrationPlan To(Guid targetState, Type migration)
|
||||
=> Add(_prevState, targetState.ToString(), migration);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the starting state.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
using Umbraco.Cms.Core.Migrations;
|
||||
|
||||
namespace Umbraco.Cms.Core.Packaging
|
||||
{
|
||||
public abstract class PackageMigrationPlan : MigrationPlan
|
||||
/// <summary>
|
||||
/// Base class for package migration plans
|
||||
/// </summary>
|
||||
public abstract class PackageMigrationPlan : MigrationPlan, IDiscoverable
|
||||
{
|
||||
protected PackageMigrationPlan(string name) : base(name)
|
||||
{
|
||||
// A call to From must be done first
|
||||
From(string.Empty);
|
||||
|
||||
DefinePlan();
|
||||
}
|
||||
|
||||
protected abstract void DefinePlan();
|
||||
}
|
||||
}
|
||||
|
||||
15
src/Umbraco.Core/Packaging/PackageMigrationPlanCollection.cs
Normal file
15
src/Umbraco.Core/Packaging/PackageMigrationPlanCollection.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
|
||||
namespace Umbraco.Cms.Core.Packaging
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of <see cref="PackageMigrationPlan"/>
|
||||
/// </summary>
|
||||
public class PackageMigrationPlanCollection : BuilderCollectionBase<PackageMigrationPlan>
|
||||
{
|
||||
public PackageMigrationPlanCollection(IEnumerable<PackageMigrationPlan> items) : base(items)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
|
||||
namespace Umbraco.Cms.Core.Packaging
|
||||
{
|
||||
public class PackageMigrationPlanCollectionBuilder : LazyCollectionBuilderBase<PackageMigrationPlanCollectionBuilder, PackageMigrationPlanCollection, PackageMigrationPlan>
|
||||
{
|
||||
protected override PackageMigrationPlanCollectionBuilder This => this;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,15 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Core.Persistence.Repositories
|
||||
{
|
||||
public interface IKeyValueRepository : IReadRepository<string, IKeyValue>, IWriteRepository<IKeyValue>
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns key/value pairs for all keys with the specified prefix.
|
||||
/// </summary>
|
||||
/// <param name="keyPrefix"></param>
|
||||
/// <returns></returns>
|
||||
IReadOnlyDictionary<string, string> FindByKeyPrefix(string keyPrefix);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,14 @@
|
||||
/// </summary>
|
||||
Upgrade = 3,
|
||||
|
||||
/// <summary>
|
||||
/// The runtime has detected that Package Migrations need to be executed.
|
||||
/// </summary>
|
||||
PackageMigrations = 4,
|
||||
|
||||
/// <summary>
|
||||
/// The runtime has detected an up-to-date Umbraco install and is running.
|
||||
/// </summary>
|
||||
Run = 4
|
||||
Run = 100
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Umbraco.Cms.Core
|
||||
namespace Umbraco.Cms.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the reason for the runtime level.
|
||||
@@ -65,6 +65,11 @@
|
||||
/// </summary>
|
||||
UpgradeMigrations,
|
||||
|
||||
/// <summary>
|
||||
/// Umbraco runs the current version but some package migrations have not run.
|
||||
/// </summary>
|
||||
UpgradePackageMigrations,
|
||||
|
||||
/// <summary>
|
||||
/// Umbraco is running.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
namespace Umbraco.Cms.Core.Services
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages the simplified key/value store.
|
||||
@@ -11,6 +14,13 @@
|
||||
/// <remarks>Returns <c>null</c> if no value was found for the key.</remarks>
|
||||
string GetValue(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Returns key/value pairs for all keys with the specified prefix.
|
||||
/// </summary>
|
||||
/// <param name="keyPrefix"></param>
|
||||
/// <returns></returns>
|
||||
IReadOnlyDictionary<string, string> FindByKeyPrefix(string keyPrefix);
|
||||
|
||||
/// <summary>
|
||||
/// Sets a value.
|
||||
/// </summary>
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Umbraco.Cms.Core.Cache
|
||||
/// <inheritdoc/>
|
||||
public void Handle(UmbracoApplicationStartingNotification notification)
|
||||
{
|
||||
if (_runtimeState.Level < RuntimeLevel.Run)
|
||||
if (_runtimeState.Level != RuntimeLevel.Run)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ namespace Umbraco.Cms.Infrastructure.Examine
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanRun() => _mainDom.IsMainDom && _runtimeState.Level >= RuntimeLevel.Run;
|
||||
private bool CanRun() => _mainDom.IsMainDom && _runtimeState.Level == RuntimeLevel.Run;
|
||||
|
||||
private void RebuildIndex(string indexName, TimeSpan delay, CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Umbraco.Cms.Infrastructure.Examine
|
||||
/// <param name="notification"></param>
|
||||
public void Handle(UmbracoRequestBeginNotification notification)
|
||||
{
|
||||
if (_runtimeState.Level < RuntimeLevel.Run)
|
||||
if (_runtimeState.Level != RuntimeLevel.Run)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -412,7 +412,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 Result UpgradeSchemaAndData(MigrationPlan plan)
|
||||
public Result UpgradeSchemaAndData(UmbracoPlan plan)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
|
||||
/// Initializes a new instance of the <see cref="UmbracoPlan"/> class.
|
||||
/// </summary>
|
||||
public UmbracoPlan(IUmbracoVersion umbracoVersion)
|
||||
: base(Cms.Core.Constants.System.UmbracoUpgradePlanName)
|
||||
: base(Core.Constants.Conventions.Migrations.UmbracoUpgradePlanName)
|
||||
{
|
||||
_umbracoVersion = umbracoVersion;
|
||||
DefinePlan();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Migrations;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
@@ -13,10 +14,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see ref="Upgrader"/> class.
|
||||
/// </summary>
|
||||
public Upgrader(MigrationPlan plan)
|
||||
{
|
||||
Plan = plan;
|
||||
}
|
||||
public Upgrader(MigrationPlan plan) => Plan = plan;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the migration plan.
|
||||
@@ -31,7 +29,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
|
||||
/// <summary>
|
||||
/// Gets the key for the state value.
|
||||
/// </summary>
|
||||
public virtual string StateValueKey => "Umbraco.Core.Upgrader.State+" + Name;
|
||||
public virtual string StateValueKey => Constants.Conventions.Migrations.KeyValuePrefix + Name;
|
||||
|
||||
/// <summary>
|
||||
/// Executes.
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using System;
|
||||
using System;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Persistence.Mappers
|
||||
{
|
||||
|
||||
[MapperFor(typeof(PublicAccessEntry))]
|
||||
public sealed class AccessMapper : BaseMapper
|
||||
{
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Persistence.Mappers
|
||||
{
|
||||
[MapperFor(typeof(KeyValue))]
|
||||
[MapperFor(typeof(IKeyValue))]
|
||||
public sealed class KeyValueMapper : BaseMapper
|
||||
{
|
||||
public KeyValueMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
|
||||
: base(sqlContext, maps)
|
||||
{ }
|
||||
|
||||
protected override void DefineMaps()
|
||||
{
|
||||
DefineMap<KeyValue, KeyValueDto>(nameof(KeyValue.Identifier), nameof(KeyValueDto.Key));
|
||||
DefineMap<KeyValue, KeyValueDto>(nameof(KeyValue.Value), nameof(KeyValueDto.Value));
|
||||
DefineMap<KeyValue, KeyValueDto>(nameof(KeyValue.UpdateDate), nameof(KeyValueDto.UpdateDate));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Mappers
|
||||
Add<DictionaryMapper>();
|
||||
Add<DictionaryTranslationMapper>();
|
||||
Add<DomainMapper>();
|
||||
Add<KeyValueMapper>();
|
||||
Add<LanguageMapper>();
|
||||
Add<MacroMapper>();
|
||||
Add<MediaMapper>();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -453,19 +453,19 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Querying
|
||||
else
|
||||
goto case "Contains**String";
|
||||
|
||||
case "SqlWildcard":
|
||||
case nameof(SqlExpressionExtensions.SqlWildcard):
|
||||
case "StartsWith":
|
||||
case "EndsWith":
|
||||
case "Contains**String": // see "Contains" above
|
||||
case "Equals":
|
||||
case "SqlStartsWith":
|
||||
case "SqlEndsWith":
|
||||
case "SqlContains":
|
||||
case "SqlEquals":
|
||||
case "InvariantStartsWith":
|
||||
case "InvariantEndsWith":
|
||||
case "InvariantContains":
|
||||
case "InvariantEquals":
|
||||
case nameof(SqlExpressionExtensions.SqlStartsWith):
|
||||
case nameof(SqlExpressionExtensions.SqlEndsWith):
|
||||
case nameof(SqlExpressionExtensions.SqlContains):
|
||||
case nameof(SqlExpressionExtensions.SqlEquals):
|
||||
case nameof(StringExtensions.InvariantStartsWith):
|
||||
case nameof(StringExtensions.InvariantEndsWith):
|
||||
case nameof(StringExtensions.InvariantContains):
|
||||
case nameof(StringExtensions.InvariantEquals):
|
||||
|
||||
string compareValue;
|
||||
|
||||
@@ -699,31 +699,31 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Querying
|
||||
{
|
||||
switch (verb)
|
||||
{
|
||||
case "SqlWildcard":
|
||||
case nameof(SqlExpressionExtensions.SqlWildcard):
|
||||
SqlParameters.Add(RemoveQuote(val));
|
||||
return Visited ? string.Empty : SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
|
||||
|
||||
case "Equals":
|
||||
case "InvariantEquals":
|
||||
case "SqlEquals":
|
||||
case nameof(StringExtensions.InvariantEquals):
|
||||
case nameof(SqlExpressionExtensions.SqlEquals):
|
||||
SqlParameters.Add(RemoveQuote(val));
|
||||
return Visited ? string.Empty : SqlSyntax.GetStringColumnEqualComparison(col, SqlParameters.Count - 1, columnType);
|
||||
|
||||
case "StartsWith":
|
||||
case "InvariantStartsWith":
|
||||
case "SqlStartsWith":
|
||||
case nameof(StringExtensions.InvariantStartsWith):
|
||||
case nameof(SqlExpressionExtensions.SqlStartsWith):
|
||||
SqlParameters.Add(RemoveQuote(val) + SqlSyntax.GetWildcardPlaceholder());
|
||||
return Visited ? string.Empty : SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
|
||||
|
||||
case "EndsWith":
|
||||
case "InvariantEndsWith":
|
||||
case "SqlEndsWith":
|
||||
case nameof(StringExtensions.InvariantEndsWith):
|
||||
case nameof(SqlExpressionExtensions.SqlEndsWith):
|
||||
SqlParameters.Add(SqlSyntax.GetWildcardPlaceholder() + RemoveQuote(val));
|
||||
return Visited ? string.Empty : SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
|
||||
|
||||
case "Contains":
|
||||
case "InvariantContains":
|
||||
case "SqlContains":
|
||||
case nameof(StringExtensions.InvariantContains):
|
||||
case nameof(SqlExpressionExtensions.SqlContains):
|
||||
var wildcardPlaceholder = SqlSyntax.GetWildcardPlaceholder();
|
||||
SqlParameters.Add(wildcardPlaceholder + RemoveQuote(val) + wildcardPlaceholder);
|
||||
return Visited ? string.Empty : SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Umbraco.Extensions;
|
||||
@@ -25,10 +25,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Querying
|
||||
return (value ?? fallbackValue).Equals(other ?? fallbackValue);
|
||||
}
|
||||
|
||||
public static bool SqlIn<T>(this IEnumerable<T> collection, T item)
|
||||
{
|
||||
return collection.Contains(item);
|
||||
}
|
||||
public static bool SqlIn<T>(this IEnumerable<T> collection, T item) => collection.Contains(item);
|
||||
|
||||
public static bool SqlWildcard(this string str, string txt, TextColumnType columnType)
|
||||
{
|
||||
@@ -39,24 +36,14 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Querying
|
||||
return wildcardmatch.IsMatch(str);
|
||||
}
|
||||
|
||||
public static bool SqlContains(this string str, string txt, TextColumnType columnType)
|
||||
{
|
||||
return str.InvariantContains(txt);
|
||||
}
|
||||
#pragma warning disable IDE0060 // Remove unused parameter
|
||||
public static bool SqlContains(this string str, string txt, TextColumnType columnType) => str.InvariantContains(txt);
|
||||
|
||||
public static bool SqlEquals(this string str, string txt, TextColumnType columnType)
|
||||
{
|
||||
return str.InvariantEquals(txt);
|
||||
}
|
||||
public static bool SqlEquals(this string str, string txt, TextColumnType columnType) => str.InvariantEquals(txt);
|
||||
|
||||
public static bool SqlStartsWith(this string str, string txt, TextColumnType columnType)
|
||||
{
|
||||
return str.InvariantStartsWith(txt);
|
||||
}
|
||||
public static bool SqlStartsWith(this string str, string txt, TextColumnType columnType) => str.InvariantStartsWith(txt);
|
||||
|
||||
public static bool SqlEndsWith(this string str, string txt, TextColumnType columnType)
|
||||
{
|
||||
return str.InvariantEndsWith(txt);
|
||||
}
|
||||
public static bool SqlEndsWith(this string str, string txt, TextColumnType columnType) => str.InvariantEndsWith(txt);
|
||||
#pragma warning restore IDE0060 // Remove unused parameter
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using Umbraco.Cms.Core.Persistence.Querying;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Querying;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
@@ -19,6 +20,11 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
: base(scopeAccessor, AppCaches.NoCache, logger)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyDictionary<string, string> FindByKeyPrefix(string keyPrefix)
|
||||
=> Get(Query<IKeyValue>().Where(entity => entity.Identifier.StartsWith(keyPrefix)))
|
||||
.ToDictionary(x => x.Identifier, x => x.Value);
|
||||
|
||||
#region Overrides of IReadWriteQueryRepository<string, IKeyValue>
|
||||
|
||||
public override void Save(IKeyValue entity)
|
||||
@@ -47,15 +53,9 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
return sql;
|
||||
}
|
||||
|
||||
protected override string GetBaseWhereClause()
|
||||
{
|
||||
return Cms.Core.Constants.DatabaseSchema.Tables.KeyValue + ".key = @id";
|
||||
}
|
||||
protected override string GetBaseWhereClause() => Core.Constants.DatabaseSchema.Tables.KeyValue + ".key = @id";
|
||||
|
||||
protected override IEnumerable<string> GetDeleteClauses()
|
||||
{
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
protected override IEnumerable<string> GetDeleteClauses() => Enumerable.Empty<string>();
|
||||
|
||||
protected override IKeyValue PerformGet(string id)
|
||||
{
|
||||
@@ -73,7 +73,10 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
|
||||
protected override IEnumerable<IKeyValue> PerformGetByQuery(IQuery<IKeyValue> query)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
var sqlClause = GetBaseQuery(false);
|
||||
var translator = new SqlTranslator<IKeyValue>(sqlClause, query);
|
||||
var sql = translator.Translate();
|
||||
return Database.Fetch<KeyValueDto>(sql).Select(Map);
|
||||
}
|
||||
|
||||
protected override void PersistNewItem(IKeyValue entity)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Persistence
|
||||
@@ -9,24 +11,36 @@ namespace Umbraco.Cms.Infrastructure.Persistence
|
||||
{
|
||||
public static UmbracoDatabase AsUmbracoDatabase(this IUmbracoDatabase database)
|
||||
{
|
||||
var asDatabase = database as UmbracoDatabase;
|
||||
if (asDatabase == null) throw new Exception("oops: database.");
|
||||
if (database is not UmbracoDatabase asDatabase)
|
||||
{
|
||||
throw new Exception("oops: database.");
|
||||
}
|
||||
|
||||
return asDatabase;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a key/value directly from the database, no scope, nothing.
|
||||
/// Gets a dictionary of key/values directly from the database, no scope, nothing.
|
||||
/// </summary>
|
||||
/// <remarks>Used by <see cref="CoreRuntimeBootstrapper"/> to determine the runtime state.</remarks>
|
||||
public static string GetFromKeyValueTable(this IUmbracoDatabase database, string key)
|
||||
public static IReadOnlyDictionary<string, string> GetFromKeyValueTable(this IUmbracoDatabase database, string keyPrefix)
|
||||
{
|
||||
if (database is null) return null;
|
||||
|
||||
// create the wildcard where clause
|
||||
ISqlSyntaxProvider sqlSyntax = database.SqlContext.SqlSyntax;
|
||||
var whereParam = sqlSyntax.GetStringColumnWildcardComparison(
|
||||
sqlSyntax.GetQuotedColumnName("key"),
|
||||
0,
|
||||
Querying.TextColumnType.NVarchar);
|
||||
|
||||
var sql = database.SqlContext.Sql()
|
||||
.Select<KeyValueDto>()
|
||||
.From<KeyValueDto>()
|
||||
.Where<KeyValueDto>(x => x.Key == key);
|
||||
return database.FirstOrDefault<KeyValueDto>(sql)?.Value;
|
||||
.Where(whereParam, keyPrefix + sqlSyntax.GetWildcardPlaceholder());
|
||||
|
||||
return database.Fetch<KeyValueDto>(sql)
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using NPoco;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
|
||||
@@ -33,6 +33,8 @@ namespace Umbraco.Cms.Infrastructure.Persistence
|
||||
{
|
||||
var columnInfo = base.GetColumnInfo(mi, type);
|
||||
|
||||
// TODO: Is this upgrade flag still relevant? It's a lot of hacking to just set this value
|
||||
// including the interface method ConfigureForUpgrade for this one circumstance.
|
||||
if (_upgrading)
|
||||
{
|
||||
if (type == typeof(UserDto) && mi.Name == "TourData") columnInfo.IgnoreColumn = true;
|
||||
|
||||
@@ -15,6 +15,7 @@ using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.Migrations.Install;
|
||||
using Umbraco.Cms.Infrastructure.Migrations.Upgrade;
|
||||
using Umbraco.Cms.Infrastructure.Persistence;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Runtime
|
||||
{
|
||||
@@ -106,7 +107,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime
|
||||
DoUnattendedInstall();
|
||||
DetermineRuntimeLevel();
|
||||
|
||||
if (State.Level <= RuntimeLevel.BootFailed)
|
||||
if (!State.UmbracoCanBoot())
|
||||
{
|
||||
return; // The exception will be rethrown by BootFailedMiddelware
|
||||
}
|
||||
@@ -117,17 +118,14 @@ namespace Umbraco.Cms.Infrastructure.Runtime
|
||||
throw new InvalidOperationException($"An instance of {typeof(IApplicationShutdownRegistry)} could not be resolved from the container, ensure that one if registered in your runtime before calling {nameof(IRuntime)}.{nameof(StartAsync)}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// if level is Run and reason is UpgradeMigrations, that means we need to perform an unattended upgrade
|
||||
if (State.Reason == RuntimeLevelReason.UpgradeMigrations && State.Level == RuntimeLevel.Run)
|
||||
if (State.RunUnattendedBootLogic())
|
||||
{
|
||||
// do the upgrade
|
||||
DoUnattendedUpgrade();
|
||||
|
||||
// upgrade is done, set reason to Run
|
||||
DetermineRuntimeLevel();
|
||||
|
||||
}
|
||||
|
||||
// create & initialize the components
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -7,6 +9,7 @@ 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;
|
||||
@@ -27,6 +30,7 @@ namespace Umbraco.Cms.Core
|
||||
private readonly ILogger<RuntimeState> _logger;
|
||||
private readonly DatabaseSchemaCreatorFactory _databaseSchemaCreatorFactory;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly PackageMigrationPlanCollection _packageMigrationPlans;
|
||||
|
||||
/// <summary>
|
||||
/// The initial <see cref="RuntimeState"/>
|
||||
@@ -48,7 +52,8 @@ namespace Umbraco.Cms.Core
|
||||
IUmbracoDatabaseFactory databaseFactory,
|
||||
ILogger<RuntimeState> logger,
|
||||
DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory,
|
||||
IEventAggregator eventAggregator)
|
||||
IEventAggregator eventAggregator,
|
||||
PackageMigrationPlanCollection packageMigrationPlans)
|
||||
{
|
||||
_globalSettings = globalSettings;
|
||||
_unattendedSettings = unattendedSettings;
|
||||
@@ -57,6 +62,7 @@ namespace Umbraco.Cms.Core
|
||||
_logger = logger;
|
||||
_databaseSchemaCreatorFactory = databaseSchemaCreatorFactory;
|
||||
_eventAggregator = eventAggregator;
|
||||
_packageMigrationPlans = packageMigrationPlans;
|
||||
}
|
||||
|
||||
|
||||
@@ -101,55 +107,62 @@ namespace Umbraco.Cms.Core
|
||||
|
||||
switch (GetUmbracoDatabaseState(_databaseFactory))
|
||||
{
|
||||
case UmbracoDatabaseState.CannotConnect:
|
||||
{
|
||||
// cannot connect to configured database, this is bad, fail
|
||||
_logger.LogDebug("Could not connect to database.");
|
||||
case UmbracoDatabaseState.CannotConnect:
|
||||
{
|
||||
// cannot connect to configured database, this is bad, fail
|
||||
_logger.LogDebug("Could not connect to database.");
|
||||
|
||||
if (_globalSettings.Value.InstallMissingDatabase)
|
||||
{
|
||||
// ok to install on a configured but missing database
|
||||
Level = RuntimeLevel.Install;
|
||||
Reason = RuntimeLevelReason.InstallMissingDatabase;
|
||||
return;
|
||||
}
|
||||
|
||||
// else it is bad enough that we want to throw
|
||||
Reason = RuntimeLevelReason.BootFailedCannotConnectToDatabase;
|
||||
BootFailedException =new BootFailedException("A connection string is configured but Umbraco could not connect to the database.");
|
||||
throw BootFailedException;
|
||||
}
|
||||
case UmbracoDatabaseState.NotInstalled:
|
||||
if (_globalSettings.Value.InstallMissingDatabase)
|
||||
{
|
||||
// ok to install on an empty database
|
||||
// ok to install on a configured but missing database
|
||||
Level = RuntimeLevel.Install;
|
||||
Reason = RuntimeLevelReason.InstallEmptyDatabase;
|
||||
Reason = RuntimeLevelReason.InstallMissingDatabase;
|
||||
return;
|
||||
}
|
||||
case UmbracoDatabaseState.NeedsUpgrade:
|
||||
{
|
||||
// the db version does not match... but we do have a migration table
|
||||
// so, at least one valid table, so we quite probably are installed & need to upgrade
|
||||
|
||||
// although the files version matches the code version, the database version does not
|
||||
// which means the local files have been upgraded but not the database - need to upgrade
|
||||
_logger.LogDebug("Has not reached the final upgrade step, need to upgrade Umbraco.");
|
||||
Level = _unattendedSettings.Value.UpgradeUnattended ? RuntimeLevel.Run : RuntimeLevel.Upgrade;
|
||||
Reason = RuntimeLevelReason.UpgradeMigrations;
|
||||
}
|
||||
// else it is bad enough that we want to throw
|
||||
Reason = RuntimeLevelReason.BootFailedCannotConnectToDatabase;
|
||||
BootFailedException = new BootFailedException("A connection string is configured but Umbraco could not connect to the database.");
|
||||
throw BootFailedException;
|
||||
}
|
||||
case UmbracoDatabaseState.NotInstalled:
|
||||
{
|
||||
// ok to install on an empty database
|
||||
Level = RuntimeLevel.Install;
|
||||
Reason = RuntimeLevelReason.InstallEmptyDatabase;
|
||||
return;
|
||||
}
|
||||
case UmbracoDatabaseState.NeedsUpgrade:
|
||||
{
|
||||
// the db version does not match... but we do have a migration table
|
||||
// so, at least one valid table, so we quite probably are installed & need to upgrade
|
||||
|
||||
// although the files version matches the code version, the database version does not
|
||||
// which means the local files have been upgraded but not the database - need to upgrade
|
||||
_logger.LogDebug("Has not reached the final upgrade step, need to upgrade Umbraco.");
|
||||
Level = _unattendedSettings.Value.UpgradeUnattended ? RuntimeLevel.Run : RuntimeLevel.Upgrade;
|
||||
Reason = RuntimeLevelReason.UpgradeMigrations;
|
||||
}
|
||||
break;
|
||||
case UmbracoDatabaseState.NeedsPackageMigration:
|
||||
|
||||
_logger.LogDebug("Package migrations need to execute.");
|
||||
Level = _unattendedSettings.Value.PackageMigrationsUnattended ? RuntimeLevel.Run : RuntimeLevel.PackageMigrations;
|
||||
Reason = RuntimeLevelReason.UpgradePackageMigrations;
|
||||
|
||||
break;
|
||||
case UmbracoDatabaseState.Ok:
|
||||
default:
|
||||
{
|
||||
// if we already know we want to upgrade, exit here
|
||||
if (Level == RuntimeLevel.Upgrade)
|
||||
return;
|
||||
{
|
||||
// if we already know we want to upgrade, exit here
|
||||
if (Level == RuntimeLevel.Upgrade)
|
||||
return;
|
||||
|
||||
// the database version matches the code & files version, all clear, can run
|
||||
Level = RuntimeLevel.Run;
|
||||
Reason = RuntimeLevelReason.Run;
|
||||
}
|
||||
break;
|
||||
// the database version matches the code & files version, all clear, can run
|
||||
Level = RuntimeLevel.Run;
|
||||
Reason = RuntimeLevelReason.Run;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +171,8 @@ namespace Umbraco.Cms.Core
|
||||
Ok,
|
||||
CannotConnect,
|
||||
NotInstalled,
|
||||
NeedsUpgrade
|
||||
NeedsUpgrade,
|
||||
NeedsPackageMigration
|
||||
}
|
||||
|
||||
private UmbracoDatabaseState GetUmbracoDatabaseState(IUmbracoDatabaseFactory databaseFactory)
|
||||
@@ -178,10 +192,24 @@ namespace Umbraco.Cms.Core
|
||||
return UmbracoDatabaseState.NotInstalled;
|
||||
}
|
||||
|
||||
if (DoesUmbracoRequireUpgrade(database))
|
||||
// Make ONE SQL call to determine Umbraco upgrade vs package migrations state.
|
||||
// All will be prefixed with the same key.
|
||||
IReadOnlyDictionary<string, string> keyValues = database.GetFromKeyValueTable(Constants.Conventions.Migrations.KeyValuePrefix);
|
||||
|
||||
// This could need both an upgrade AND package migrations to execute but
|
||||
// we will process them one at a time, first the upgrade, then the package migrations.
|
||||
if (DoesUmbracoRequireUpgrade(keyValues))
|
||||
{
|
||||
return UmbracoDatabaseState.NeedsUpgrade;
|
||||
}
|
||||
|
||||
|
||||
// TODO: Can we save the result of this since we'll need to re-use it?
|
||||
IReadOnlyList<string> packagesRequiringMigration = DoesUmbracoRequirePackageMigrations(keyValues);
|
||||
if (packagesRequiringMigration.Count > 0)
|
||||
{
|
||||
return UmbracoDatabaseState.NeedsPackageMigration;
|
||||
}
|
||||
}
|
||||
|
||||
return UmbracoDatabaseState.Ok;
|
||||
@@ -206,31 +234,46 @@ namespace Umbraco.Cms.Core
|
||||
|
||||
public void DoUnattendedInstall()
|
||||
{
|
||||
// unattended install is not enabled
|
||||
if (_unattendedSettings.Value.InstallUnattended == false) return;
|
||||
// unattended install is not enabled
|
||||
if (_unattendedSettings.Value.InstallUnattended == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// no connection string set
|
||||
if (_databaseFactory.Configured == false) return;
|
||||
if (_databaseFactory.Configured == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var connect = false;
|
||||
var tries = _globalSettings.Value.InstallMissingDatabase ? 2 : 5;
|
||||
for (var i = 0;;)
|
||||
|
||||
bool connect;
|
||||
for (var i = 0; ;)
|
||||
{
|
||||
connect = _databaseFactory.CanConnect;
|
||||
if (connect || ++i == tries) break;
|
||||
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;
|
||||
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;
|
||||
if (hasUmbracoTables)
|
||||
return;
|
||||
|
||||
// all conditions fulfilled, do the install
|
||||
_logger.LogInformation("Starting unattended install.");
|
||||
@@ -263,12 +306,14 @@ namespace Umbraco.Cms.Core
|
||||
}
|
||||
}
|
||||
|
||||
private bool DoesUmbracoRequireUpgrade(IUmbracoDatabase database)
|
||||
private bool DoesUmbracoRequireUpgrade(IReadOnlyDictionary<string, string> keyValues)
|
||||
{
|
||||
var upgrader = new Upgrader(new UmbracoPlan(_umbracoVersion));
|
||||
var stateValueKey = upgrader.StateValueKey;
|
||||
|
||||
CurrentMigrationState = database.GetFromKeyValueTable(stateValueKey);
|
||||
_ = keyValues.TryGetValue(stateValueKey, out var value);
|
||||
|
||||
CurrentMigrationState = value;
|
||||
FinalMigrationState = upgrader.Plan.FinalState;
|
||||
|
||||
_logger.LogDebug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", FinalMigrationState, CurrentMigrationState ?? "<null>");
|
||||
@@ -276,6 +321,41 @@ namespace Umbraco.Cms.Core
|
||||
return CurrentMigrationState != FinalMigrationState;
|
||||
}
|
||||
|
||||
private IReadOnlyList<string> DoesUmbracoRequirePackageMigrations(IReadOnlyDictionary<string, string> keyValues)
|
||||
{
|
||||
var packageMigrationPlans = _packageMigrationPlans.ToList();
|
||||
|
||||
var result = new List<string>(packageMigrationPlans.Count);
|
||||
|
||||
foreach(PackageMigrationPlan plan in packageMigrationPlans)
|
||||
{
|
||||
string currentMigrationState = null;
|
||||
var planKeyValueKey = Constants.Conventions.Migrations.KeyValuePrefix + plan.Name;
|
||||
if (keyValues.TryGetValue(planKeyValueKey, out var value))
|
||||
{
|
||||
currentMigrationState = value;
|
||||
|
||||
if (plan.FinalState != value)
|
||||
{
|
||||
// Not equal so we need to run
|
||||
result.Add(plan.Name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If there is nothing in the DB then we need to run
|
||||
result.Add(plan.Name);
|
||||
}
|
||||
|
||||
_logger.LogDebug("Final package migration for {PackagePlan} state is {FinalMigrationState}, database contains {DatabaseState}",
|
||||
plan.Name,
|
||||
plan.FinalState,
|
||||
currentMigrationState ?? "<null>");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool TryDbConnect(IUmbracoDatabaseFactory databaseFactory)
|
||||
{
|
||||
// anything other than install wants a database - see if we can connect
|
||||
@@ -285,7 +365,8 @@ namespace Umbraco.Cms.Core
|
||||
for (var i = 0; ;)
|
||||
{
|
||||
canConnect = databaseFactory.CanConnect;
|
||||
if (canConnect || ++i == tries) break;
|
||||
if (canConnect || ++i == tries)
|
||||
break;
|
||||
_logger.LogDebug("Could not immediately connect to database, trying again.");
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
@@ -25,6 +26,15 @@ namespace Umbraco.Cms.Core.Services.Implement
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyDictionary<string, string> FindByKeyPrefix(string keyPrefix)
|
||||
{
|
||||
using (var scope = _scopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
return _repository.FindByKeyPrefix(keyPrefix);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetValue(string key, string value)
|
||||
{
|
||||
|
||||
100
src/Umbraco.Tests.Integration/Umbraco.Core/RuntimeStateTests.cs
Normal file
100
src/Umbraco.Tests.Integration/Umbraco.Core/RuntimeStateTests.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Packaging;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.Migrations;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
using Umbraco.Cms.Tests.Integration.Testing;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
|
||||
public class RuntimeStateTests : UmbracoIntegrationTest
|
||||
{
|
||||
private protected IRuntimeState RuntimeState { get; private set; }
|
||||
|
||||
public override void Configure(IApplicationBuilder app)
|
||||
{
|
||||
base.Configure(app);
|
||||
|
||||
RuntimeState = Services.GetRequiredService<IRuntimeState>();
|
||||
}
|
||||
|
||||
protected override void CustomTestSetup(IUmbracoBuilder builder)
|
||||
{
|
||||
PackageMigrationPlanCollectionBuilder migrations = builder.PackageMigrationPlans();
|
||||
migrations.Clear();
|
||||
migrations.Add<TestMigrationPlan>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GivenPackageMigrationsExist_WhenLatestStateIsRegistered_ThenLevelIsRun()
|
||||
{
|
||||
// Add the final state to the keyvalue storage
|
||||
IKeyValueService keyValueService = Services.GetRequiredService<IKeyValueService>();
|
||||
keyValueService.SetValue(
|
||||
Constants.Conventions.Migrations.KeyValuePrefix + TestMigrationPlan.TestMigrationPlanName,
|
||||
TestMigrationPlan.TestMigrationFinalState.ToString());
|
||||
|
||||
RuntimeState.DetermineRuntimeLevel();
|
||||
|
||||
Assert.AreEqual(RuntimeLevel.Run, RuntimeState.Level);
|
||||
Assert.AreEqual(RuntimeLevelReason.Run, RuntimeState.Reason);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GivenPackageMigrationsExist_WhenUnattendedMigrations_ThenLevelIsRun()
|
||||
{
|
||||
RuntimeState.DetermineRuntimeLevel();
|
||||
|
||||
Assert.AreEqual(RuntimeLevel.Run, RuntimeState.Level);
|
||||
Assert.AreEqual(RuntimeLevelReason.UpgradePackageMigrations, RuntimeState.Reason);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GivenPackageMigrationsExist_WhenNotUnattendedMigrations_ThenLevelIsPackageMigrations()
|
||||
{
|
||||
var unattendedOptions = Services.GetRequiredService<IOptions<UnattendedSettings>>();
|
||||
unattendedOptions.Value.PackageMigrationsUnattended = false;
|
||||
|
||||
RuntimeState.DetermineRuntimeLevel();
|
||||
|
||||
Assert.AreEqual(RuntimeLevel.PackageMigrations, RuntimeState.Level);
|
||||
Assert.AreEqual(RuntimeLevelReason.UpgradePackageMigrations, RuntimeState.Reason);
|
||||
}
|
||||
|
||||
private class TestMigrationPlan : PackageMigrationPlan
|
||||
{
|
||||
public const string TestMigrationPlanName = "Test";
|
||||
public static Guid TestMigrationFinalState => new Guid("BB02C392-4007-4A6C-A550-28BA2FF7E43D");
|
||||
|
||||
public TestMigrationPlan() : base(TestMigrationPlanName)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void DefinePlan()
|
||||
{
|
||||
To<TestMigration>(TestMigrationFinalState);
|
||||
}
|
||||
}
|
||||
|
||||
private class TestMigration : MigrationBase
|
||||
{
|
||||
public TestMigration(IMigrationContext context) : base(context)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
@@ -19,6 +20,27 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
|
||||
{
|
||||
private IKeyValueService KeyValueService => GetRequiredService<IKeyValueService>();
|
||||
|
||||
[Test]
|
||||
public void Can_Query_For_Key_Prefix()
|
||||
{
|
||||
// Arrange
|
||||
KeyValueService.SetValue("test1", "hello1");
|
||||
KeyValueService.SetValue("test2", "hello2");
|
||||
KeyValueService.SetValue("test3", "hello3");
|
||||
KeyValueService.SetValue("test4", "hello4");
|
||||
KeyValueService.SetValue("someotherprefix1", "helloagain1");
|
||||
// Act
|
||||
IReadOnlyDictionary<string, string> value = KeyValueService.FindByKeyPrefix("test");
|
||||
|
||||
// Assert
|
||||
|
||||
Assert.AreEqual(4, value.Count);
|
||||
Assert.AreEqual("hello1", value["test1"]);
|
||||
Assert.AreEqual("hello2", value["test2"]);
|
||||
Assert.AreEqual("hello3", value["test3"]);
|
||||
Assert.AreEqual("hello4", value["test4"]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetValue_ForMissingKey_ReturnsNull()
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Web.BackOffice.Authorization
|
||||
{
|
||||
@@ -30,8 +31,7 @@ namespace Umbraco.Cms.Web.BackOffice.Authorization
|
||||
|
||||
switch (_runtimeState.Level)
|
||||
{
|
||||
case RuntimeLevel.Install:
|
||||
case RuntimeLevel.Upgrade:
|
||||
case var _ when _runtimeState.EnableInstaller():
|
||||
return Task.FromResult(true);
|
||||
default:
|
||||
if (!_backOfficeSecurity.BackOfficeSecurity.IsAuthenticated())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Umbraco.Cms.Core;
|
||||
@@ -29,9 +29,8 @@ namespace Umbraco.Cms.Web.BackOffice.Install
|
||||
|
||||
switch (_runtime.Level)
|
||||
{
|
||||
case RuntimeLevel.Install:
|
||||
case RuntimeLevel.Upgrade:
|
||||
|
||||
case var _ when _runtime.EnableInstaller():
|
||||
|
||||
endpoints.MapUmbracoRoute<InstallApiController>(installPathSegment, Cms.Core.Constants.Web.Mvc.InstallArea, "api", includeControllerNameInRoute: false);
|
||||
endpoints.MapUmbracoRoute<InstallController>(installPathSegment, Cms.Core.Constants.Web.Mvc.InstallArea, string.Empty, includeControllerNameInRoute: false);
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using System;
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Web.BackOffice.Install
|
||||
{
|
||||
@@ -44,8 +45,7 @@ namespace Umbraco.Cms.Web.BackOffice.Install
|
||||
{
|
||||
// if not configured (install or upgrade) then we can continue
|
||||
// otherwise we need to ensure that a user is logged in
|
||||
return _runtimeState.Level == RuntimeLevel.Install
|
||||
|| _runtimeState.Level == RuntimeLevel.Upgrade
|
||||
return _runtimeState.EnableInstaller()
|
||||
|| (authorizationFilterContext.HttpContext.User?.Identity?.IsAuthenticated ?? false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -48,6 +48,7 @@ namespace Umbraco.Cms.Web.BackOffice.Routing
|
||||
{
|
||||
case RuntimeLevel.Install:
|
||||
case RuntimeLevel.Upgrade:
|
||||
case RuntimeLevel.PackageMigrations:
|
||||
case RuntimeLevel.Run:
|
||||
|
||||
MapMinimalBackOffice(endpoints);
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace Umbraco.Cms.Web.BackOffice.Routing
|
||||
{
|
||||
case RuntimeLevel.Install:
|
||||
case RuntimeLevel.Upgrade:
|
||||
case RuntimeLevel.PackageMigrations:
|
||||
case RuntimeLevel.Run:
|
||||
endpoints.MapHub<PreviewHub>(GetPreviewHubRoute());
|
||||
endpoints.MapUmbracoRoute<PreviewController>(_umbracoPathSegment, Constants.Web.Mvc.BackOfficeArea, null);
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace Umbraco.Cms.Web.Common.Profiler
|
||||
|
||||
public void UmbracoApplicationBeginRequest(HttpContext context, RuntimeLevel runtimeLevel)
|
||||
{
|
||||
if (runtimeLevel < RuntimeLevel.Run)
|
||||
if (runtimeLevel != RuntimeLevel.Run)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -55,7 +55,7 @@ namespace Umbraco.Cms.Web.Common.Profiler
|
||||
|
||||
public void UmbracoApplicationEndRequest(HttpContext context, RuntimeLevel runtimeLevel)
|
||||
{
|
||||
if (runtimeLevel < RuntimeLevel.Run)
|
||||
if (runtimeLevel != RuntimeLevel.Run)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user