Merge pull request #4080 from umbraco/temp8-auto-install

install for Deploy
This commit is contained in:
Shannon Deminick
2019-01-17 01:44:27 +11:00
committed by GitHub
13 changed files with 213 additions and 30 deletions

View File

@@ -76,11 +76,13 @@ namespace Umbraco.Core.Components
var composerTypeList = _composerTypes
.Where(x =>
{
// use the min level specified by the attribute if any
// otherwise, user composers have Run min level, anything else is Unknown (always run)
// use the min/max levels specified by the attribute if any
// otherwise, min: user composers are Run, anything else is Unknown (always run)
// max: everything is Run (always run)
var attr = x.GetCustomAttribute<RuntimeLevelAttribute>();
var minLevel = attr?.MinLevel ?? (x.Implements<IUserComposer>() ? RuntimeLevel.Run : RuntimeLevel.Unknown);
return _composition.RuntimeState.Level >= minLevel;
var maxLevel = attr?.MaxLevel ?? RuntimeLevel.Run;
return _composition.RuntimeState.Level >= minLevel && _composition.RuntimeState.Level <= maxLevel;
})
.ToList();

View File

@@ -1,13 +1,22 @@
using System;
using Umbraco.Core.Composing;
namespace Umbraco.Core.Components
{
/// <summary>
/// Marks a composer to indicate a minimum and/or maximum runtime level for which the composer would compose.
/// </summary>
[AttributeUsage(AttributeTargets.Class /*, AllowMultiple = false, Inherited = true*/)]
public class RuntimeLevelAttribute : Attribute
{
//public RuntimeLevelAttribute()
//{ }
/// <summary>
/// Gets or sets the minimum runtime level for which the composer would compose.
/// </summary>
public RuntimeLevel MinLevel { get; set; } = RuntimeLevel.Install;
public RuntimeLevel MinLevel { get; set; } = RuntimeLevel.Boot;
/// <summary>
/// Gets or sets the maximum runtime level for which the composer would compose.
/// </summary>
public RuntimeLevel MaxLevel { get; set; } = RuntimeLevel.Run;
}
}

View File

@@ -57,6 +57,11 @@ namespace Umbraco.Core
/// </summary>
RuntimeLevel Level { get; }
/// <summary>
/// Gets the reason for the runtime level of execution.
/// </summary>
RuntimeLevelReason Reason { get; }
/// <summary>
/// Gets the current migration state.
/// </summary>

View File

@@ -14,7 +14,7 @@ namespace Umbraco.Core.Migrations.Install
/// <summary>
/// Creates the initial database schema during install.
/// </summary>
internal class DatabaseSchemaCreator
public class DatabaseSchemaCreator
{
private readonly IUmbracoDatabase _database;
private readonly ILogger _logger;
@@ -28,7 +28,7 @@ namespace Umbraco.Core.Migrations.Install
private ISqlSyntaxProvider SqlSyntax => _database.SqlContext.SqlSyntax;
// all tables, in order
public static readonly List<Type> OrderedTables = new List<Type>
internal static readonly List<Type> OrderedTables = new List<Type>
{
typeof (UserDto),
typeof (NodeDto),
@@ -138,7 +138,7 @@ namespace Umbraco.Core.Migrations.Install
/// <summary>
/// Validates the schema of the current database.
/// </summary>
public DatabaseSchemaResult ValidateSchema()
internal DatabaseSchemaResult ValidateSchema()
{
var result = new DatabaseSchemaResult(SqlSyntax);
@@ -387,7 +387,7 @@ namespace Umbraco.Core.Migrations.Install
/// If <typeparamref name="T"/> has been decorated with an <see cref="TableNameAttribute"/>, the name from that
/// attribute will be used for the table name. If the attribute is not present, the name
/// <typeparamref name="T"/> will be used instead.
///
///
/// If a table with the same name already exists, the <paramref name="overwrite"/> parameter will determine
/// whether the table is overwritten. If <c>true</c>, the table will be overwritten, whereas this method will
/// not do anything if the parameter is <c>false</c>.
@@ -409,14 +409,14 @@ namespace Umbraco.Core.Migrations.Install
/// If <paramref name="modelType"/> has been decorated with an <see cref="TableNameAttribute"/>, the name from
/// that attribute will be used for the table name. If the attribute is not present, the name
/// <paramref name="modelType"/> will be used instead.
///
///
/// If a table with the same name already exists, the <paramref name="overwrite"/> parameter will determine
/// whether the table is overwritten. If <c>true</c>, the table will be overwritten, whereas this method will
/// not do anything if the parameter is <c>false</c>.
///
/// This need to execute as part of a transaction.
/// </remarks>
public void CreateTable(bool overwrite, Type modelType, DatabaseDataCreator dataCreation)
internal void CreateTable(bool overwrite, Type modelType, DatabaseDataCreator dataCreation)
{
if (!_database.InTransaction)
throw new InvalidOperationException("Database is not in a transaction.");

View File

@@ -18,6 +18,12 @@ namespace Umbraco.Core.Persistence
/// </summary>
bool Configured { get; }
/// <summary>
/// Gets the connection string.
/// </summary>
/// <remarks>Throws if the factory is not configured.</remarks>
string ConnectionString { get; }
/// <summary>
/// Gets a value indicating whether the database can connect.
/// </summary>

View File

@@ -2,6 +2,7 @@
using System.Configuration;
using System.Data.Common;
using System.Threading;
using LightInject;
using NPoco;
using NPoco.FluentMappings;
using Umbraco.Core.Exceptions;
@@ -102,6 +103,16 @@ namespace Umbraco.Core.Persistence
/// <inheritdoc />
public bool Configured { get; private set; }
/// <inheritdoc />
public string ConnectionString
{
get
{
EnsureConfigured();
return _connectionString;
}
}
/// <inheritdoc />
public bool CanConnect
{

View File

@@ -252,7 +252,7 @@ namespace Umbraco.Core.Runtime
{
_state.DetermineRuntimeLevel(databaseFactory, profilingLogger);
profilingLogger.Debug<CoreRuntime>("Runtime level: {RuntimeLevel}", _state.Level);
profilingLogger.Debug<CoreRuntime>("Runtime level: {RuntimeLevel} - {RuntimeLevelReason}", _state.Level, _state.Reason);
if (_state.Level == RuntimeLevel.Upgrade)
{
@@ -263,6 +263,7 @@ namespace Umbraco.Core.Runtime
catch
{
_state.Level = RuntimeLevel.BootFailed;
_state.Reason = RuntimeLevelReason.BootFailedOnException;
timer.Fail();
throw;
}

View File

@@ -1,5 +1,8 @@
namespace Umbraco.Core
{
/// <summary>
/// Describes the levels in which the runtime can run.
/// </summary>
public enum RuntimeLevel
{
/// <summary>

View File

@@ -0,0 +1,68 @@
namespace Umbraco.Core
{
/// <summary>
/// Describes the reason for the runtime level.
/// </summary>
public enum RuntimeLevelReason
{
/// <summary>
/// The code version is lower than the version indicated in web.config, and
/// downgrading Umbraco is not supported.
/// </summary>
BootFailedCannotDowngrade,
/// <summary>
/// The runtime cannot connect to the configured database.
/// </summary>
BootFailedCannotConnectToDatabase,
/// <summary>
/// The runtime can connect to the configured database, but it cannot
/// retrieve the migrations status.
/// </summary>
BootFailedCannotCheckUpgradeState,
/// <summary>
/// An exception was thrown during boot.
/// </summary>
BootFailedOnException,
/// <summary>
/// Umbraco is not installed at all.
/// </summary>
InstallNoVersion,
/// <summary>
/// A version is specified in web.config but the database is not configured.
/// </summary>
/// <remarks>This is a weird state.</remarks>
InstallNoDatabase,
/// <summary>
/// A version is specified in web.config and a database is configured, but the
/// database is missing, and installing over a missing database has been enabled.
/// </summary>
InstallMissingDatabase,
/// <summary>
/// A version is specified in web.config and a database is configured, but the
/// database is empty, and installing over an empty database has been enabled.
/// </summary>
InstallEmptyDatabase,
/// <summary>
/// Umbraco runs an old version.
/// </summary>
UpgradeOldVersion,
/// <summary>
/// Umbraco runs the current version but some migrations have not run.
/// </summary>
UpgradeMigrations,
/// <summary>
/// Umbraco is running.
/// </summary>
Run
}
}

View File

@@ -93,6 +93,9 @@ namespace Umbraco.Core
internal set { _level = value; if (value == RuntimeLevel.Run) _runLevel.Set(); }
}
/// <inheritdoc />
public RuntimeLevelReason Reason { get; internal set; }
/// <summary>
/// Ensures that the <see cref="ApplicationUrl"/> property has a value.
/// </summary>
@@ -143,6 +146,7 @@ namespace Umbraco.Core
// there is no local version, we are not installed
logger.Debug<RuntimeState>("No local version, need to install Umbraco.");
Level = RuntimeLevel.Install;
Reason = RuntimeLevelReason.InstallNoVersion;
return;
}
@@ -152,12 +156,14 @@ namespace Umbraco.Core
// need to upgrade
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);
// in fact, this is bad enough that we want to throw
Reason = RuntimeLevelReason.BootFailedCannotDowngrade;
throw new BootFailedException($"Local version \"{localVersion}\" > code version \"{codeVersion}\", downgrading is not supported.");
}
else if (databaseFactory.Configured == false)
@@ -166,16 +172,18 @@ namespace Umbraco.Core
// install (again? this is a weird situation...)
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)
for (var i = 0; i < 5; i++)
var tries = RuntimeStateOptions.InstallMissingDatabase ? 2 : 5;
for (var i = 0;;)
{
connect = databaseFactory.CanConnect;
if (connect) break;
if (connect || ++i == tries) break;
logger.Debug<RuntimeState>("Could not immediately connect to database, trying again.");
Thread.Sleep(1000);
}
@@ -185,7 +193,16 @@ namespace Umbraco.Core
// cannot connect to configured database, this is bad, fail
logger.Debug<RuntimeState>("Could not connect to database.");
// in fact, this is bad enough that we want to throw
if (RuntimeStateOptions.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.");
}
@@ -195,7 +212,6 @@ namespace Umbraco.Core
// else
// look for a matching migration entry - bypassing services entirely - they are not 'up' yet
// fixme - in a LB scenario, ensure that the DB gets upgraded only once!
bool noUpgrade;
try
{
@@ -205,6 +221,17 @@ namespace Umbraco.Core
{
// can connect to the database but cannot check the upgrade state... oops
logger.Warn<RuntimeState>(e, "Could not check the upgrade state.");
if (RuntimeStateOptions.InstallEmptyDatabase)
{
// ok to install on an empty database
Level = RuntimeLevel.Install;
Reason = RuntimeLevelReason.InstallEmptyDatabase;
return;
}
// else it is bad enough that we want to throw
Reason = RuntimeLevelReason.BootFailedCannotCheckUpgradeState;
throw new BootFailedException("Could not check the upgrade state.", e);
}
@@ -216,6 +243,7 @@ namespace Umbraco.Core
{
// the database version matches the code & files version, all clear, can run
Level = RuntimeLevel.Run;
Reason = RuntimeLevelReason.Run;
return;
}
@@ -226,6 +254,7 @@ namespace Umbraco.Core
// 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)

View File

@@ -0,0 +1,40 @@
using System.Configuration;
namespace Umbraco.Core
{
/// <summary>
/// Allows configuration of the <see cref="RuntimeState"/> in PreApplicationStart or in appSettings
/// </summary>
public static class RuntimeStateOptions
{
// configured statically or via app settings
private static bool BoolSetting(string key, bool missing) => ConfigurationManager.AppSettings[key]?.InvariantEquals("true") ?? missing;
/// <summary>
/// If true the RuntimeState will continue the installation sequence when a database is missing
/// </summary>
/// <remarks>
/// In this case it will be up to the implementor that is setting this value to true to take over the bootup/installation sequence
/// </remarks>
public static bool InstallMissingDatabase
{
get => _installEmptyDatabase ?? BoolSetting("Umbraco.Core.RuntimeState.InstallMissingDatabase", false);
set => _installEmptyDatabase = value;
}
/// <summary>
/// If true the RuntimeState will continue the installation sequence when a database is available but is empty
/// </summary>
/// <remarks>
/// In this case it will be up to the implementor that is setting this value to true to take over the bootup/installation sequence
/// </remarks>
public static bool InstallEmptyDatabase
{
get => _installMissingDatabase ?? BoolSetting("Umbraco.Core.RuntimeState.InstallEmptyDatabase", false);
set => _installMissingDatabase = value;
}
private static bool? _installMissingDatabase;
private static bool? _installEmptyDatabase;
}
}

View File

@@ -511,6 +511,8 @@
<Compile Include="PropertyEditors\VoidEditor.cs" />
<Compile Include="ReadLock.cs" />
<Compile Include="ReflectionUtilities-Unused.cs" />
<Compile Include="RuntimeLevelReason.cs" />
<Compile Include="RuntimeStateOptions.cs" />
<Compile Include="Runtime\CoreRuntime.cs" />
<Compile Include="Runtime\CoreRuntimeComponent.cs" />
<Compile Include="CustomBooleanTypeConverter.cs" />

View File

@@ -1,4 +1,4 @@
angular.module("umbraco.install").factory('installerService', function($rootScope, $q, $timeout, $http, $location, $log){
angular.module("umbraco.install").factory('installerService', function ($rootScope, $q, $timeout, $http, $templateRequest){
var _status = {
index: 0,
@@ -106,19 +106,26 @@ angular.module("umbraco.install").factory('installerService', function($rootScop
//loads the needed steps and sets the intial state
init : function(){
service.status.loading = true;
if(!_status.all){
service.getSteps().then(function(response){
service.status.steps = response.data.steps;
service.status.index = 0;
_installerModel.installId = response.data.installId;
service.findNextStep();
if (!_status.all) {
//pre-load the error page, if an error occurs, the page might not be able to load
// so we want to make sure it's available in the templatecache first
$templateRequest("views/install/error.html").then(x => {
service.getSteps().then(response => {
service.status.steps = response.data.steps;
service.status.index = 0;
_installerModel.installId = response.data.installId;
service.findNextStep();
$timeout(function(){
service.status.loading = false;
service.status.configuring = true;
}, 2000);
});
}
$timeout(function() {
service.status.loading = false;
service.status.configuring = true;
},
2000);
});
});
}
},
//loads available packages from our.umbraco.com