Add runtime hooks for Deploy

This commit is contained in:
Stephan
2019-01-23 21:22:49 +01:00
parent b9fd04c8b9
commit 31a6f2d67c
7 changed files with 172 additions and 74 deletions

View File

@@ -283,48 +283,60 @@ namespace Umbraco.Core.Migrations.Install
if (string.IsNullOrWhiteSpace(connectionString)) throw new ArgumentNullOrEmptyException(nameof(connectionString));
if (string.IsNullOrWhiteSpace(providerName)) throw new ArgumentNullOrEmptyException(nameof(providerName));
// set the connection string for the new datalayer
var connectionStringSettings = new ConnectionStringSettings(Constants.System.UmbracoConnectionName, connectionString, providerName);
var fileSource = "web.config";
var fileName = IOHelper.MapPath(Path.Combine(SystemDirectories.Root, fileSource));
var fileName = IOHelper.MapPath($"{SystemDirectories.Root}/web.config");
var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace);
if (xml.Root == null) throw new Exception("Invalid web.config file.");
var connectionStrings = xml.Root.DescendantsAndSelf("connectionStrings").FirstOrDefault();
if (connectionStrings == null) throw new Exception("Invalid web.config file.");
if (xml.Root == null) throw new Exception($"Invalid {fileSource} file (no root).");
// honour configSource, if its set, change the xml file we are saving the configuration
// to the one set in the configSource attribute
if (connectionStrings.Attribute("configSource") != null)
var connectionStrings = xml.Root.DescendantsAndSelf("connectionStrings").FirstOrDefault();
if (connectionStrings == null) throw new Exception($"Invalid {fileSource} file (no connection strings).");
// handle configSource
var configSourceAttribute = connectionStrings.Attribute("configSource");
if (configSourceAttribute != null)
{
var source = connectionStrings.Attribute("configSource").Value;
var configFile = IOHelper.MapPath($"{SystemDirectories.Root}/{source}");
logger.Info<DatabaseBuilder>("Storing ConnectionString in {ConfigFile}", configFile);
if (File.Exists(configFile))
{
xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace);
fileName = configFile;
}
fileSource = configSourceAttribute.Value;
fileName = IOHelper.MapPath(Path.Combine(SystemDirectories.Root, fileSource));
if (!File.Exists(fileName))
throw new Exception($"Invalid configSource \"{fileSource}\" (no such file).");
xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace);
if (xml.Root == null) throw new Exception($"Invalid {fileSource} file (no root).");
connectionStrings = xml.Root.DescendantsAndSelf("connectionStrings").FirstOrDefault();
if (connectionStrings == null) throw new Exception("Invalid web.config file.");
if (connectionStrings == null) throw new Exception($"Invalid {fileSource} file (no connection strings).");
}
// update connectionString if it exists, or else create a new connectionString
var setting = connectionStrings.Descendants("add").FirstOrDefault(s => s.Attribute("name").Value == Constants.System.UmbracoConnectionName);
// create or update connection string
var setting = connectionStrings.Descendants("add").FirstOrDefault(s => s.Attribute("name")?.Value == Constants.System.UmbracoConnectionName);
if (setting == null)
{
connectionStrings.Add(new XElement("add",
new XAttribute("name", Constants.System.UmbracoConnectionName),
new XAttribute("connectionString", connectionStringSettings),
new XAttribute("connectionString", connectionString),
new XAttribute("providerName", providerName)));
}
else
{
setting.Attribute("connectionString").Value = connectionString;
setting.Attribute("providerName").Value = providerName;
AddOrUpdateAttribute(setting, "connectionString", connectionString);
AddOrUpdateAttribute(setting, "providerName", providerName);
}
// save
logger.Info<DatabaseBuilder>("Saving connection string to {ConfigFile}.", fileSource);
xml.Save(fileName, SaveOptions.DisableFormatting);
logger.Info<DatabaseBuilder>("Configured a new ConnectionString using the '{ProviderName}' provider.", providerName);
logger.Info<DatabaseBuilder>("Saved connection string to {ConfigFile}.", fileSource);
}
private static void AddOrUpdateAttribute(XElement element, string name, string value)
{
var attribute = element.Attribute(name);
if (attribute == null)
element.Add(new XAttribute(name, value));
else
attribute.Value = value;
}
internal bool IsConnectionStringConfigured(ConnectionStringSettings databaseSettings)
@@ -422,7 +434,7 @@ namespace Umbraco.Core.Migrations.Install
_logger.Info<DatabaseBuilder>("Database configuration status: Started");
var database = scope.Database;
var message = string.Empty;
var schemaResult = ValidateSchema();
@@ -482,7 +494,7 @@ namespace Umbraco.Core.Migrations.Install
}
_logger.Info<DatabaseBuilder>("Database upgrade started");
// upgrade
var upgrader = new UmbracoUpgrader();
upgrader.Execute(_scopeProvider, _migrationBuilder, _keyValueService, _logger, _postMigrations);

View File

@@ -59,14 +59,18 @@ namespace Umbraco.Core.Persistence
/// <remarks>Used by the other ctor and in tests.</remarks>
public UmbracoDatabaseFactory(string connectionStringName, ILogger logger, Lazy<IMapperCollection> mappers)
{
if (string.IsNullOrWhiteSpace(connectionStringName)) throw new ArgumentNullOrEmptyException(nameof(connectionStringName));
if (string.IsNullOrWhiteSpace(connectionStringName))
throw new ArgumentNullOrEmptyException(nameof(connectionStringName));
_mappers = mappers ?? throw new ArgumentNullException(nameof(mappers));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
var settings = ConfigurationManager.ConnectionStrings[connectionStringName];
if (settings == null)
{
logger.Debug<UmbracoDatabaseFactory>("Missing connection string, defer configuration.");
return; // not configured
}
// could as well be <add name="umbracoDbDSN" connectionString="" providerName="" />
// so need to test the values too
@@ -74,7 +78,7 @@ namespace Umbraco.Core.Persistence
var providerName = settings.ProviderName;
if (string.IsNullOrWhiteSpace(connectionString) || string.IsNullOrWhiteSpace(providerName))
{
logger.Debug<UmbracoDatabaseFactory>("Missing connection string or provider name, defer configuration.");
logger.Debug<UmbracoDatabaseFactory>("Empty connection string or provider name, defer configuration.");
return; // not configured
}

View File

@@ -100,6 +100,9 @@ namespace Umbraco.Core.Runtime
// throws if not full-trust
new AspNetHostingPermission(AspNetHostingPermissionLevel.Unrestricted).Demand();
// run handlers
RuntimeOptions.DoRuntimeBoot(ProfilingLogger);
// application caches
var appCaches = GetAppCaches();
@@ -131,12 +134,17 @@ namespace Umbraco.Core.Runtime
composition = new Composition(register, typeLoader, ProfilingLogger, _state, configs);
composition.RegisterEssentials(Logger, Profiler, ProfilingLogger, mainDom, appCaches, databaseFactory, typeLoader, _state);
// run handlers
RuntimeOptions.DoRuntimeEssentials(composition, appCaches, typeLoader, databaseFactory);
// register runtime-level services
// there should be none, really - this is here "just in case"
Compose(composition);
// acquire the main domain, determine our runtime level
// acquire the main domain
AcquireMainDom(mainDom);
// determine our runtime level
DetermineRuntimeLevel(databaseFactory, ProfilingLogger);
// get composers, and compose

View File

@@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Runtime.CompilerServices;
using Umbraco.Core.Cache;
using Umbraco.Core.Components;
using Umbraco.Core.Composing;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
namespace Umbraco.Core
{
/// <summary>
/// Provides static options for the runtime.
/// </summary>
/// <remarks>
/// These options can be configured in PreApplicationStart or via appSettings.
/// </remarks>
public static class RuntimeOptions
{
private static List<Action<IProfilingLogger>> _onBoot;
private static List<Action<Composition, AppCaches, TypeLoader, IUmbracoDatabaseFactory>> _onEssentials;
private static bool? _installMissingDatabase;
private static bool? _installEmptyDatabase;
// reads a boolean appSetting
private static bool BoolSetting(string key, bool missing) => ConfigurationManager.AppSettings[key]?.InvariantEquals("true") ?? missing;
/// <summary>
/// Gets a value indicating whether the runtime should enter Install level when the database is missing.
/// </summary>
/// <remarks>
/// <para>By default, when a database connection string is configured but it is not possible to
/// connect to the database, the runtime enters the BootFailed level. If this options is set to true,
/// it enters the Install level instead.</para>
/// <para>It is then up to the implementor, that is setting this value, to take over the installation
/// sequence.</para>
/// </remarks>
public static bool InstallMissingDatabase
{
get => _installEmptyDatabase ?? BoolSetting("Umbraco.Core.RuntimeState.InstallMissingDatabase", false);
set => _installEmptyDatabase = 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 BootFailed level. If this options
/// is set to true, it enters the Install level instead.</para>
/// <para>It is then up to the implementor, that is setting this value, to take over the installation
/// sequence.</para>
/// </remarks>
public static bool InstallEmptyDatabase
{
get => _installMissingDatabase ?? BoolSetting("Umbraco.Core.RuntimeState.InstallEmptyDatabase", false);
set => _installMissingDatabase = value;
}
/// <summary>
/// Executes the RuntimeBoot handlers.
/// </summary>
internal static void DoRuntimeBoot(IProfilingLogger logger)
{
if (_onBoot == null)
return;
foreach (var action in _onBoot)
action(logger);
}
/// <summary>
/// Executes the RuntimeEssentials handlers.
/// </summary>
internal static void DoRuntimeEssentials(Composition composition, AppCaches appCaches, TypeLoader typeLoader, IUmbracoDatabaseFactory databaseFactory)
{
if (_onEssentials== null)
return;
foreach (var action in _onEssentials)
action(composition, appCaches, typeLoader, databaseFactory);
}
/// <summary>
/// Registers a RuntimeBoot handler.
/// </summary>
/// <remarks>
/// <para>A RuntimeBoot handler runs when the runtime boots, right after the
/// loggers have been created, but before anything else.</para>
/// </remarks>
public static void OnRuntimeBoot(Action<IProfilingLogger> action)
{
if (_onBoot == null)
_onBoot = new List<Action<IProfilingLogger>>();
_onBoot.Add(action);
}
/// <summary>
/// Registers a RuntimeEssentials handler.
/// </summary>
/// <remarks>
/// <para>A RuntimeEssentials handler runs after the runtime has created a few
/// essential things (AppCaches, a TypeLoader, and a database factory) but
/// before anything else.</para>
/// </remarks>
public static void OnRuntimeEssentials(Action<Composition, AppCaches, TypeLoader, IUmbracoDatabaseFactory> action)
{
if (_onEssentials == null)
_onEssentials = new List<Action<Composition, AppCaches, TypeLoader, IUmbracoDatabaseFactory>>();
_onEssentials.Add(action);
}
}
}

View File

@@ -169,7 +169,7 @@ namespace Umbraco.Core
else if (databaseFactory.Configured == false)
{
// local version *does* match code version, but the database is not configured
// install (again? this is a weird situation...)
// install - may happen with Deploy/Cloud/etc
logger.Debug<RuntimeState>("Database is not configured, need to install Umbraco.");
Level = RuntimeLevel.Install;
Reason = RuntimeLevelReason.InstallNoDatabase;
@@ -179,7 +179,7 @@ namespace Umbraco.Core
// 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 = RuntimeStateOptions.InstallMissingDatabase ? 2 : 5;
var tries = RuntimeOptions.InstallMissingDatabase ? 2 : 5;
for (var i = 0;;)
{
connect = databaseFactory.CanConnect;
@@ -193,7 +193,7 @@ namespace Umbraco.Core
// cannot connect to configured database, this is bad, fail
logger.Debug<RuntimeState>("Could not connect to database.");
if (RuntimeStateOptions.InstallMissingDatabase)
if (RuntimeOptions.InstallMissingDatabase)
{
// ok to install on a configured but missing database
Level = RuntimeLevel.Install;
@@ -222,7 +222,7 @@ 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)
if (RuntimeOptions.InstallEmptyDatabase)
{
// ok to install on an empty database
Level = RuntimeLevel.Install;

View File

@@ -1,40 +0,0 @@
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

@@ -530,7 +530,7 @@
<Compile Include="ReadLock.cs" />
<Compile Include="ReflectionUtilities-Unused.cs" />
<Compile Include="RuntimeLevelReason.cs" />
<Compile Include="RuntimeStateOptions.cs" />
<Compile Include="RuntimeOptions.cs" />
<Compile Include="Runtime\CoreRuntime.cs" />
<Compile Include="Runtime\CoreRuntimeComponent.cs" />
<Compile Include="CustomBooleanTypeConverter.cs" />