2017-07-20 11:21:28 +02:00
using System ;
2016-09-01 19:06:08 +02:00
using System.Threading ;
using Semver ;
2020-09-16 14:29:33 +02:00
using Microsoft.Extensions.Logging ;
2020-11-19 20:05:28 +00:00
using Microsoft.Extensions.Options ;
2016-09-01 19:06:08 +02:00
using Umbraco.Core.Configuration ;
2020-08-21 14:52:47 +01:00
using Umbraco.Core.Configuration.Models ;
2017-05-31 15:25:07 +02:00
using Umbraco.Core.Exceptions ;
2021-01-18 15:40:22 +01:00
using Umbraco.Core.Migrations.Install ;
2018-12-17 18:52:43 +01:00
using Umbraco.Core.Migrations.Upgrade ;
using Umbraco.Core.Persistence ;
2016-09-01 19:06:08 +02:00
namespace Umbraco.Core
{
/// <summary>
/// Represents the state of the Umbraco runtime.
/// </summary>
2019-12-18 13:05:34 +01:00
public class RuntimeState : IRuntimeState
2016-09-01 19:06:08 +02:00
{
2020-08-21 14:52:47 +01:00
private readonly GlobalSettings _globalSettings ;
2019-11-14 11:47:34 +01:00
private readonly IUmbracoVersion _umbracoVersion ;
2020-09-02 18:10:29 +10:00
private readonly IUmbracoDatabaseFactory _databaseFactory ;
2020-09-16 14:29:33 +02:00
private readonly ILogger < RuntimeState > _logger ;
2021-01-18 15:40:22 +01:00
private readonly DatabaseSchemaCreatorFactory _databaseSchemaCreatorFactory ;
2020-09-02 18:10:29 +10:00
/// <summary>
/// The initial <see cref="RuntimeState"/>
/// </summary>
public static RuntimeState Booting ( ) = > new RuntimeState ( ) { Level = RuntimeLevel . Boot } ;
private RuntimeState ( )
{
}
2016-09-01 19:06:08 +02:00
/// <summary>
/// Initializes a new instance of the <see cref="RuntimeState"/> class.
/// </summary>
2021-01-18 15:40:22 +01:00
public RuntimeState (
IOptions < GlobalSettings > globalSettings ,
IUmbracoVersion umbracoVersion ,
IUmbracoDatabaseFactory databaseFactory ,
ILogger < RuntimeState > logger ,
DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory )
2016-09-01 19:06:08 +02:00
{
2020-11-19 20:05:28 +00:00
_globalSettings = globalSettings . Value ;
2019-11-14 11:47:34 +01:00
_umbracoVersion = umbracoVersion ;
2020-09-02 18:10:29 +10:00
_databaseFactory = databaseFactory ;
_logger = logger ;
2021-01-18 15:40:22 +01:00
_databaseSchemaCreatorFactory = databaseSchemaCreatorFactory ;
2016-09-01 19:06:08 +02:00
}
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2019-11-14 11:47:34 +01:00
public Version Version = > _umbracoVersion . Current ;
2016-09-01 19:06:08 +02:00
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2019-11-14 11:47:34 +01:00
public string VersionComment = > _umbracoVersion . Comment ;
2016-09-01 19:06:08 +02:00
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2019-11-14 11:47:34 +01:00
public SemVersion SemanticVersion = > _umbracoVersion . SemanticVersion ;
2016-09-01 19:06:08 +02:00
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2020-03-25 15:06:22 +11:00
public string CurrentMigrationState { get ; private set ; }
2018-03-29 11:31:33 +02:00
/// <inheritdoc />
2020-03-25 15:06:22 +11:00
public string FinalMigrationState { get ; private set ; }
2018-03-29 11:31:33 +02:00
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2019-01-30 08:35:19 +01:00
public RuntimeLevel Level { get ; internal set ; } = RuntimeLevel . Unknown ;
2016-09-01 19:06:08 +02:00
2019-01-10 14:03:25 +01:00
/// <inheritdoc />
2019-01-30 08:35:19 +01:00
public RuntimeLevelReason Reason { get ; internal set ; } = RuntimeLevelReason . Unknown ;
2019-01-10 14:03:25 +01:00
2018-11-26 16:54:32 +01:00
/// <inheritdoc />
2017-05-31 15:25:07 +02:00
public BootFailedException BootFailedException { get ; internal set ; }
2018-12-17 18:52:43 +01:00
2020-09-02 18:10:29 +10:00
/// <inheritdoc />
public void DetermineRuntimeLevel ( )
2018-12-17 18:52:43 +01:00
{
2020-09-02 18:10:29 +10:00
if ( _databaseFactory . Configured = = false )
2018-12-17 18:52:43 +01:00
{
// local version *does* match code version, but the database is not configured
2019-01-23 21:22:49 +01:00
// install - may happen with Deploy/Cloud/etc
2020-09-16 10:24:05 +02:00
_logger . LogDebug ( "Database is not configured, need to install Umbraco." ) ;
2018-12-17 18:52:43 +01:00
Level = RuntimeLevel . Install ;
2019-01-10 14:03:25 +01:00
Reason = RuntimeLevelReason . InstallNoDatabase ;
2018-12-17 18:52:43 +01:00
return ;
}
2021-01-18 15:40:22 +01:00
// Check the database state, whether we can connect or if it's in an upgrade or empty state, etc...
2018-12-17 18:52:43 +01:00
2021-01-18 15:40:22 +01:00
switch ( GetUmbracoDatabaseState ( _databaseFactory ) )
2018-12-17 18:52:43 +01:00
{
2021-01-18 15:40:22 +01:00
case UmbracoDatabaseState . CannotConnect :
{
// cannot connect to configured database, this is bad, fail
_logger . LogDebug ( "Could not connect to database." ) ;
2018-12-17 18:52:43 +01:00
2021-01-18 15:40:22 +01:00
if ( _globalSettings . InstallMissingDatabase )
{
// ok to install on a configured but missing database
Level = RuntimeLevel . Install ;
Reason = RuntimeLevelReason . InstallMissingDatabase ;
return ;
}
2019-01-10 14:03:25 +01:00
2021-01-18 15:40:22 +01:00
// 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 . LogDebug ( "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 ;
2018-12-17 18:52:43 +01:00
}
2021-01-18 15:40:22 +01:00
}
2018-12-17 18:52:43 +01:00
2021-01-18 15:40:22 +01:00
private enum UmbracoDatabaseState
{
Ok ,
CannotConnect ,
NotInstalled ,
NeedsUpgrade
}
2018-12-17 18:52:43 +01:00
2021-01-18 15:40:22 +01:00
private UmbracoDatabaseState GetUmbracoDatabaseState ( IUmbracoDatabaseFactory databaseFactory )
{
2018-12-17 18:52:43 +01:00
try
{
2021-01-18 15:40:22 +01:00
if ( ! TryDbConnect ( databaseFactory ) )
{
return UmbracoDatabaseState . CannotConnect ;
}
// no scope, no service - just directly accessing the database
using ( var database = databaseFactory . CreateDatabase ( ) )
{
if ( ! database . IsUmbracoInstalled ( ) )
{
return UmbracoDatabaseState . NotInstalled ;
}
if ( DoesUmbracoRequireUpgrade ( database ) )
{
return UmbracoDatabaseState . NeedsUpgrade ;
}
}
return UmbracoDatabaseState . Ok ;
2018-12-17 18:52:43 +01:00
}
catch ( Exception e )
{
2021-01-18 15:40:22 +01:00
// can connect to the database so cannot check the upgrade state... oops
2020-09-16 09:58:07 +02:00
_logger . LogWarning ( e , "Could not check the upgrade state." ) ;
2019-01-10 14:03:25 +01:00
// else it is bad enough that we want to throw
Reason = RuntimeLevelReason . BootFailedCannotCheckUpgradeState ;
2018-12-17 18:52:43 +01:00
throw new BootFailedException ( "Could not check the upgrade state." , e ) ;
}
2021-01-18 15:40:22 +01:00
}
2018-12-17 18:52:43 +01:00
2021-01-18 15:40:22 +01:00
public void Configure ( RuntimeLevel level , RuntimeLevelReason reason )
{
Level = level ;
Reason = reason ;
}
public void DoUnattendedInstall ( )
{
// unattended install is not enabled
if ( _globalSettings . InstallUnattended = = false ) return ;
2019-01-04 10:29:29 +01:00
2021-01-18 15:40:22 +01:00
// no connection string set
if ( _databaseFactory . Configured = = false ) return ;
var connect = false ;
var tries = _globalSettings . InstallMissingDatabase ? 2 : 5 ;
for ( var i = 0 ; ; )
2018-12-17 18:52:43 +01:00
{
2021-01-18 15:40:22 +01:00
connect = _databaseFactory . CanConnect ;
if ( connect | | + + i = = tries ) break ;
_logger . LogDebug ( "Could not immediately connect to database, trying again." ) ;
Thread . Sleep ( 1000 ) ;
2018-12-17 18:52:43 +01:00
}
2021-01-18 15:40:22 +01:00
// could not connect to the database
if ( connect = = false ) return ;
2018-12-17 18:52:43 +01:00
2021-01-18 15:40:22 +01:00
using ( var database = _databaseFactory . CreateDatabase ( ) )
{
var hasUmbracoTables = database . IsUmbracoInstalled ( ) ;
2018-12-17 18:52:43 +01:00
2021-01-18 15:40:22 +01:00
// database has umbraco tables, assume Umbraco is already installed
if ( hasUmbracoTables ) return ;
// all conditions fulfilled, do the install
_logger . LogInformation ( "Starting unattended install." ) ;
try
{
database . BeginTransaction ( ) ;
var creator = _databaseSchemaCreatorFactory . Create ( database ) ;
creator . InitializeDatabaseSchema ( ) ;
database . CompleteTransaction ( ) ;
_logger . LogInformation ( "Unattended install completed." ) ;
}
catch ( Exception ex )
{
_logger . LogInformation ( ex , "Error during unattended install." ) ;
database . AbortTransaction ( ) ;
throw new UnattendedInstallException (
"The database configuration failed with the following message: " + ex . Message
+ "\n Please check log file for additional information (can be found in '/App_Data/Logs/')" ) ;
}
}
2020-11-24 09:22:38 +00:00
}
2020-03-25 15:06:22 +11:00
private bool EnsureUmbracoUpgradeState ( IUmbracoDatabaseFactory databaseFactory , ILogger logger )
2018-12-17 18:52:43 +01:00
{
2020-09-04 14:30:48 +02:00
var upgrader = new Upgrader ( new UmbracoPlan ( _umbracoVersion ) ) ;
2018-12-21 10:58:38 +01:00
var stateValueKey = upgrader . StateValueKey ;
2018-12-17 18:52:43 +01:00
// no scope, no service - just directly accessing the database
using ( var database = databaseFactory . CreateDatabase ( ) )
{
2020-03-28 15:13:50 +01:00
CurrentMigrationState = database . GetFromKeyValueTable ( stateValueKey ) ;
2018-12-21 10:58:38 +01:00
FinalMigrationState = upgrader . Plan . FinalState ;
2018-12-17 18:52:43 +01:00
}
2020-09-16 10:24:05 +02:00
logger . LogDebug ( "Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}" , FinalMigrationState , CurrentMigrationState ? ? "<null>" ) ;
2018-12-17 18:52:43 +01:00
return CurrentMigrationState = = FinalMigrationState ;
}
2021-01-18 15:40:22 +01:00
private bool DoesUmbracoRequireUpgrade ( IUmbracoDatabase database )
{
var upgrader = new Upgrader ( new UmbracoPlan ( _umbracoVersion ) ) ;
var stateValueKey = upgrader . StateValueKey ;
CurrentMigrationState = database . GetFromKeyValueTable ( stateValueKey ) ;
FinalMigrationState = upgrader . Plan . FinalState ;
_logger . LogDebug ( "Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}" , FinalMigrationState , CurrentMigrationState ? ? "<null>" ) ;
return CurrentMigrationState ! = FinalMigrationState ;
}
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 = _globalSettings . InstallMissingDatabase ? 2 : 5 ;
for ( var i = 0 ; ; )
{
canConnect = databaseFactory . CanConnect ;
if ( canConnect | | + + i = = tries ) break ;
_logger . LogDebug ( "Could not immediately connect to database, trying again." ) ;
Thread . Sleep ( 1000 ) ;
}
return canConnect ;
}
2016-09-01 19:06:08 +02:00
}
2017-07-20 11:21:28 +02:00
}