Fixes issue with SqlMainDomLock during install, obsolete InstallEmptyDatabase, ensures installation can occur with/without the umbraco version
This commit is contained in:
@@ -446,11 +446,9 @@ namespace Umbraco.Core.Migrations.Install
|
||||
|
||||
var schemaResult = ValidateSchema();
|
||||
var hasInstalledVersion = schemaResult.DetermineHasInstalledVersion();
|
||||
//var installedSchemaVersion = schemaResult.DetermineInstalledVersion();
|
||||
//var hasInstalledVersion = !installedSchemaVersion.Equals(new Version(0, 0, 0));
|
||||
|
||||
//If Configuration Status is empty and the determined version is "empty" its a new install - otherwise upgrade the existing
|
||||
if (string.IsNullOrEmpty(_globalSettings.ConfigurationStatus) && !hasInstalledVersion)
|
||||
//If the determined version is "empty" its a new install - otherwise upgrade the existing
|
||||
if (!hasInstalledVersion)
|
||||
{
|
||||
if (_runtime.Level == RuntimeLevel.Run)
|
||||
throw new Exception("Umbraco is already configured!");
|
||||
|
||||
@@ -336,7 +336,7 @@ namespace Umbraco.Core.Runtime
|
||||
{
|
||||
try
|
||||
{
|
||||
_state.DetermineRuntimeLevel(databaseFactory, profilingLogger);
|
||||
_state.DetermineRuntimeLevel(databaseFactory);
|
||||
|
||||
profilingLogger.Debug<CoreRuntime>("Runtime level: {RuntimeLevel} - {RuntimeLevelReason}", _state.Level, _state.Reason);
|
||||
|
||||
|
||||
@@ -401,7 +401,7 @@ namespace Umbraco.Core.Runtime
|
||||
_cancellationTokenSource.Cancel();
|
||||
_cancellationTokenSource.Dispose();
|
||||
|
||||
if (_dbFactory.Configured)
|
||||
if (_dbFactory.Configured && _hasTable)
|
||||
{
|
||||
using var db = _dbFactory.CreateDatabase();
|
||||
using var transaction = db.GetTransaction(IsolationLevel.ReadCommitted);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Configuration;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Umbraco.Core.Cache;
|
||||
@@ -42,14 +43,8 @@ namespace Umbraco.Core
|
||||
set => _installMissingDatabase = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the runtime should enter Install level when the database is empty.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>By default, when a database connection string is configured and it is possible to connect to
|
||||
/// the database, but the database is empty, the runtime enters the <c>Install</c> level. If this options
|
||||
/// is set to <c>false</c>, it enters the <c>BootFailed</c> level instead.</para>
|
||||
/// </remarks>
|
||||
[Obsolete("This setting is no longer used and will be removed in future versions. If a database connection string is configured and the database is empty Umbraco will be installed during the installation sequence.")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static bool InstallEmptyDatabase
|
||||
{
|
||||
get => _installEmptyDatabase ?? BoolSetting("Umbraco.Core.RuntimeState.InstallEmptyDatabase", true);
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using System.Data.SqlServerCe;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using Semver;
|
||||
@@ -123,16 +126,15 @@ namespace Umbraco.Core
|
||||
/// <summary>
|
||||
/// Determines the runtime level.
|
||||
/// </summary>
|
||||
public void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, ILogger logger)
|
||||
public void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory)
|
||||
{
|
||||
var localVersion = UmbracoVersion.LocalVersion; // the local, files, version
|
||||
var codeVersion = SemanticVersion; // the executing code version
|
||||
var connect = false;
|
||||
|
||||
|
||||
if (localVersion == null)
|
||||
{
|
||||
// there is no local version, we are not installed
|
||||
logger.Debug<RuntimeState>("No local version, need to install Umbraco.");
|
||||
_logger.Debug<RuntimeState>("No local version, need to install Umbraco.");
|
||||
Level = RuntimeLevel.Install;
|
||||
Reason = RuntimeLevelReason.InstallNoVersion;
|
||||
return;
|
||||
@@ -142,13 +144,13 @@ namespace Umbraco.Core
|
||||
{
|
||||
// there *is* a local version, but it does not match the code version
|
||||
// need to upgrade
|
||||
logger.Debug<RuntimeState>("Local version '{LocalVersion}' < code version '{CodeVersion}', need to upgrade Umbraco.", localVersion, codeVersion);
|
||||
_logger.Debug<RuntimeState>("Local version '{LocalVersion}' < code version '{CodeVersion}', need to upgrade Umbraco.", localVersion, codeVersion);
|
||||
Level = RuntimeLevel.Upgrade;
|
||||
Reason = RuntimeLevelReason.UpgradeOldVersion;
|
||||
}
|
||||
else if (localVersion > codeVersion)
|
||||
{
|
||||
logger.Warn<RuntimeState>("Local version '{LocalVersion}' > code version '{CodeVersion}', downgrading is not supported.", localVersion, codeVersion);
|
||||
_logger.Warn<RuntimeState>("Local version '{LocalVersion}' > code version '{CodeVersion}', downgrading is not supported.", localVersion, codeVersion);
|
||||
|
||||
// in fact, this is bad enough that we want to throw
|
||||
Reason = RuntimeLevelReason.BootFailedCannotDowngrade;
|
||||
@@ -158,108 +160,139 @@ namespace Umbraco.Core
|
||||
{
|
||||
// local version *does* match code version, but the database is not configured
|
||||
// install - may happen with Deploy/Cloud/etc
|
||||
logger.Debug<RuntimeState>("Database is not configured, need to install Umbraco.");
|
||||
_logger.Debug<RuntimeState>("Database is not configured, need to install Umbraco.");
|
||||
Level = RuntimeLevel.Install;
|
||||
Reason = RuntimeLevelReason.InstallNoDatabase;
|
||||
return;
|
||||
}
|
||||
|
||||
// else, keep going,
|
||||
// anything other than install wants a database - see if we can connect
|
||||
// (since this is an already existing database, assume localdb is ready)
|
||||
var tries = RuntimeOptions.InstallMissingDatabase ? 2 : 5;
|
||||
for (var i = 0;;)
|
||||
// Check the database state, whether we can connect or if it's in an upgrade or empty state, etc...
|
||||
|
||||
switch (GetUmbracoDatabaseState(databaseFactory))
|
||||
{
|
||||
connect = databaseFactory.CanConnect;
|
||||
if (connect || ++i == tries) break;
|
||||
logger.Debug<RuntimeState>("Could not immediately connect to database, trying again.");
|
||||
Thread.Sleep(1000);
|
||||
case UmbracoDatabaseState.CannotConnect:
|
||||
{
|
||||
// cannot connect to configured database, this is bad, fail
|
||||
_logger.Debug<RuntimeState>("Could not connect to database.");
|
||||
|
||||
if (RuntimeOptions.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;
|
||||
throw new BootFailedException("A connection string is configured but Umbraco could not connect to the database.");
|
||||
}
|
||||
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.Debug<RuntimeState>("Has not reached the final upgrade step, need to upgrade Umbraco.");
|
||||
Level = RuntimeLevel.Upgrade;
|
||||
Reason = RuntimeLevelReason.UpgradeMigrations;
|
||||
}
|
||||
break;
|
||||
case UmbracoDatabaseState.Ok:
|
||||
default:
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
if (connect == false)
|
||||
{
|
||||
// cannot connect to configured database, this is bad, fail
|
||||
logger.Debug<RuntimeState>("Could not connect to database.");
|
||||
private enum UmbracoDatabaseState
|
||||
{
|
||||
Ok,
|
||||
CannotConnect,
|
||||
NotInstalled,
|
||||
NeedsUpgrade
|
||||
}
|
||||
|
||||
if (RuntimeOptions.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;
|
||||
throw new BootFailedException("A connection string is configured but Umbraco could not connect to the database.");
|
||||
}
|
||||
|
||||
// if we already know we want to upgrade,
|
||||
// still run EnsureUmbracoUpgradeState to get the states
|
||||
// (v7 will just get a null state, that's ok)
|
||||
|
||||
// else
|
||||
// look for a matching migration entry - bypassing services entirely - they are not 'up' yet
|
||||
bool noUpgrade;
|
||||
private UmbracoDatabaseState GetUmbracoDatabaseState(IUmbracoDatabaseFactory databaseFactory)
|
||||
{
|
||||
try
|
||||
{
|
||||
noUpgrade = EnsureUmbracoUpgradeState(databaseFactory, logger);
|
||||
if (!TryDbConnect(databaseFactory))
|
||||
{
|
||||
return UmbracoDatabaseState.CannotConnect;
|
||||
}
|
||||
|
||||
// no scope, no service - just directly accessing the database
|
||||
using (var database = databaseFactory.CreateDatabase())
|
||||
{
|
||||
if (!database.IsUmbracoInstalled(_logger))
|
||||
{
|
||||
return UmbracoDatabaseState.NotInstalled;
|
||||
}
|
||||
|
||||
if (DoesUmbracoRequireUpgrade(database))
|
||||
{
|
||||
return UmbracoDatabaseState.NeedsUpgrade;
|
||||
}
|
||||
}
|
||||
|
||||
return UmbracoDatabaseState.Ok;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// can connect to the database but cannot check the upgrade state... oops
|
||||
logger.Warn<RuntimeState>(e, "Could not check the upgrade state.");
|
||||
|
||||
if (RuntimeOptions.InstallEmptyDatabase)
|
||||
{
|
||||
// ok to install on an empty database
|
||||
Level = RuntimeLevel.Install;
|
||||
Reason = RuntimeLevelReason.InstallEmptyDatabase;
|
||||
return;
|
||||
}
|
||||
// can connect to the database so cannot check the upgrade state... oops
|
||||
_logger.Warn<RuntimeState>(e, "Could not check the upgrade state.");
|
||||
|
||||
// else it is bad enough that we want to throw
|
||||
Reason = RuntimeLevelReason.BootFailedCannotCheckUpgradeState;
|
||||
throw new BootFailedException("Could not check the upgrade state.", e);
|
||||
}
|
||||
|
||||
// if we already know we want to upgrade, exit here
|
||||
if (Level == RuntimeLevel.Upgrade)
|
||||
return;
|
||||
|
||||
if (noUpgrade)
|
||||
{
|
||||
// the database version matches the code & files version, all clear, can run
|
||||
Level = RuntimeLevel.Run;
|
||||
Reason = RuntimeLevelReason.Run;
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.Debug<RuntimeState>("Has not reached the final upgrade step, need to upgrade Umbraco.");
|
||||
Level = RuntimeLevel.Upgrade;
|
||||
Reason = RuntimeLevelReason.UpgradeMigrations;
|
||||
}
|
||||
|
||||
protected virtual bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger)
|
||||
private bool TryDbConnect(IUmbracoDatabaseFactory databaseFactory)
|
||||
{
|
||||
// anything other than install wants a database - see if we can connect
|
||||
// (since this is an already existing database, assume localdb is ready)
|
||||
bool canConnect;
|
||||
var tries = RuntimeOptions.InstallMissingDatabase ? 2 : 5;
|
||||
for (var i = 0; ;)
|
||||
{
|
||||
canConnect = databaseFactory.CanConnect;
|
||||
if (canConnect || ++i == tries) break;
|
||||
_logger.Debug<RuntimeState>("Could not immediately connect to database, trying again.");
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
return canConnect;
|
||||
}
|
||||
|
||||
private bool DoesUmbracoRequireUpgrade(IUmbracoDatabase database)
|
||||
{
|
||||
var upgrader = new Upgrader(new UmbracoPlan());
|
||||
var stateValueKey = upgrader.StateValueKey;
|
||||
|
||||
// no scope, no service - just directly accessing the database
|
||||
using (var database = databaseFactory.CreateDatabase())
|
||||
{
|
||||
CurrentMigrationState = KeyValueService.GetValue(database, stateValueKey);
|
||||
FinalMigrationState = upgrader.Plan.FinalState;
|
||||
}
|
||||
CurrentMigrationState = KeyValueService.GetValue(database, stateValueKey);
|
||||
FinalMigrationState = upgrader.Plan.FinalState;
|
||||
|
||||
logger.Debug<RuntimeState>("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", FinalMigrationState, CurrentMigrationState ?? "<null>");
|
||||
_logger.Debug<RuntimeState>("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", FinalMigrationState, CurrentMigrationState ?? "<null>");
|
||||
|
||||
return CurrentMigrationState == FinalMigrationState;
|
||||
return CurrentMigrationState != FinalMigrationState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Migrations.Install;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Install.Models;
|
||||
|
||||
@@ -124,32 +125,94 @@ namespace Umbraco.Web.Install.InstallSteps
|
||||
{
|
||||
get
|
||||
{
|
||||
return RequiresExecution(null)
|
||||
//the user UI
|
||||
? "user"
|
||||
//the continue install UI
|
||||
: "continueinstall";
|
||||
return ShowView()
|
||||
// the user UI
|
||||
? "user"
|
||||
// continue install UI
|
||||
: "continueinstall";
|
||||
}
|
||||
}
|
||||
|
||||
private InstallState GetInstallState()
|
||||
{
|
||||
var installState = InstallState.Unknown;
|
||||
|
||||
var databaseSettings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName];
|
||||
|
||||
var hasVersion = !_globalSettings.ConfigurationStatus.IsNullOrWhiteSpace();
|
||||
if (hasVersion)
|
||||
{
|
||||
installState = InstallState.HasVersion;
|
||||
}
|
||||
|
||||
var hasConnString = databaseSettings != null && _databaseBuilder.IsDatabaseConfigured;
|
||||
if (hasConnString)
|
||||
{
|
||||
installState = (installState | InstallState.HasConnectionString) & ~InstallState.Unknown;
|
||||
}
|
||||
|
||||
var connStringConfigured = hasConnString ? _databaseBuilder.IsConnectionStringConfigured(databaseSettings) : false;
|
||||
if (connStringConfigured)
|
||||
{
|
||||
installState = (installState | InstallState.ConnectionStringConfigured) & ~InstallState.Unknown;
|
||||
}
|
||||
|
||||
var canConnect = connStringConfigured ? DbConnectionExtensions.IsConnectionAvailable(databaseSettings.ConnectionString, databaseSettings.ProviderName) : false;
|
||||
if (canConnect)
|
||||
{
|
||||
installState = (installState | InstallState.CanConnect) & ~InstallState.Unknown;
|
||||
}
|
||||
|
||||
var umbracoInstalled = canConnect ? _databaseBuilder.IsUmbracoInstalled() : false;
|
||||
if (umbracoInstalled)
|
||||
{
|
||||
installState = (installState | InstallState.UmbracoInstalled) & ~InstallState.Unknown;
|
||||
}
|
||||
|
||||
var hasNonDefaultUser = umbracoInstalled ? _databaseBuilder.HasSomeNonDefaultUser() : false;
|
||||
if (hasNonDefaultUser)
|
||||
{
|
||||
installState = (installState | InstallState.HasNonDefaultUser) & ~InstallState.Unknown;
|
||||
}
|
||||
|
||||
return installState;
|
||||
}
|
||||
|
||||
private bool ShowView()
|
||||
{
|
||||
var installState = GetInstallState();
|
||||
|
||||
return installState.HasFlag(InstallState.Unknown)
|
||||
|| !installState.HasFlag(InstallState.UmbracoInstalled);
|
||||
}
|
||||
|
||||
public override bool RequiresExecution(UserModel model)
|
||||
{
|
||||
//now we have to check if this is really a new install, the db might be configured and might contain data
|
||||
var databaseSettings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName];
|
||||
var installState = GetInstallState();
|
||||
|
||||
//if there's already a version then there should def be a user but in some cases someone may have
|
||||
// left a version number in there but cleared out their db conn string, in that case, it's really a new install.
|
||||
if (_globalSettings.ConfigurationStatus.IsNullOrWhiteSpace() == false && databaseSettings != null) return false;
|
||||
if (installState.HasFlag(InstallState.Unknown))
|
||||
{
|
||||
// In this one case when it's a brand new install and nothing has been configured, make sure the
|
||||
// back office cookie is cleared so there's no old cookies lying around causing problems
|
||||
_http.ExpireCookie(Current.Configs.Settings().Security.AuthCookieName);
|
||||
}
|
||||
|
||||
// if Umbraco is already installed there's already users in the database, skip this step
|
||||
if (_databaseBuilder.IsConnectionStringConfigured(databaseSettings) && _databaseBuilder.IsDatabaseConfigured && _databaseBuilder.IsUmbracoInstalled())
|
||||
return _databaseBuilder.HasSomeNonDefaultUser() == false;
|
||||
return installState.HasFlag(InstallState.Unknown)
|
||||
|| !installState.HasFlag(InstallState.HasNonDefaultUser);
|
||||
}
|
||||
|
||||
// In this one case when it's a brand new install and nothing has been configured, make sure the
|
||||
// back office cookie is cleared so there's no old cookies lying around causing problems
|
||||
_http.ExpireCookie(Current.Configs.Settings().Security.AuthCookieName);
|
||||
|
||||
return true;
|
||||
[Flags]
|
||||
private enum InstallState : short
|
||||
{
|
||||
// This is an easy way to avoid 0 enum assignment and not worry about
|
||||
// manual calcs. https://www.codeproject.com/Articles/396851/Ending-the-Great-Debate-on-Enum-Flags
|
||||
Unknown = 1,
|
||||
HasVersion = 1 << 1,
|
||||
HasConnectionString = 1 << 2,
|
||||
ConnectionStringConfigured = 1 << 3,
|
||||
CanConnect = 1 << 4,
|
||||
UmbracoInstalled = 1 << 5,
|
||||
HasNonDefaultUser = 1 << 6
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user