v10: Use ForceCreateDatabase during unattended install and extend GetUmbracoConnectionString extension methods (#12397)
* Add extension methods to get the Umbraco connection string/provider name from configuration * Added tests for configuration extension methods. * Fix issue with InstallMissingDatabase and ForceCreateDatabase * Fix comments * Revert casing change in GenerateConnectionString * Re-add AddOptions (without config binding) to fix test * Update src/Umbraco.Core/Configuration/Models/ConnectionStrings.cs Co-authored-by: Ronald Barendse <ronald@barend.se> * Update src/Umbraco.Core/Configuration/Models/ConnectionStrings.cs * Update src/Umbraco.Infrastructure/Runtime/RuntimeState.cs * Whitespace and documentation updates * Add DatabaseProviderMetadataExtensions * Filter before ordering * Replace DataDirectory placeholder when setting connection string Co-authored-by: Andy Butland <abutland73@gmail.com> Co-authored-by: Bjarke Berg <mail@bergmania.dk>
This commit is contained in:
@@ -1,7 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
@@ -13,9 +9,7 @@ using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Install.InstallSteps
|
||||
{
|
||||
[InstallSetupStep(InstallationType.NewInstall,
|
||||
"DatabaseConfigure", "database", 10, "Setting up a database, so Umbraco has a place to store your website",
|
||||
PerformsAppRestart = true)]
|
||||
[InstallSetupStep(InstallationType.NewInstall, "DatabaseConfigure", "database", 10, "Setting up a database, so Umbraco has a place to store your website", PerformsAppRestart = true)]
|
||||
public class DatabaseConfigureStep : InstallSetupStep<DatabaseModel>
|
||||
{
|
||||
private readonly DatabaseBuilder _databaseBuilder;
|
||||
@@ -45,44 +39,33 @@ namespace Umbraco.Cms.Infrastructure.Install.InstallSteps
|
||||
return Task.FromResult<InstallSetupResult?>(null);
|
||||
}
|
||||
|
||||
public override object ViewModel
|
||||
public override object ViewModel => new
|
||||
{
|
||||
get
|
||||
{
|
||||
var options = _databaseProviderMetadata
|
||||
.Where(x => x.IsAvailable)
|
||||
.OrderBy(x => x.SortOrder)
|
||||
.ToList();
|
||||
databases = _databaseProviderMetadata.GetAvailable().ToList()
|
||||
};
|
||||
|
||||
return new
|
||||
{
|
||||
databases = options
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public override string View => ShouldDisplayView() ? base.View : "";
|
||||
public override string View => ShouldDisplayView() ? base.View : string.Empty;
|
||||
|
||||
public override bool RequiresExecution(DatabaseModel model) => ShouldDisplayView();
|
||||
|
||||
private bool ShouldDisplayView()
|
||||
{
|
||||
//If the connection string is already present in web.config we don't need to show the settings page and we jump to installing/upgrading.
|
||||
// If the connection string is already present in web.config we don't need to show the settings page and we jump to installing/upgrading.
|
||||
var databaseSettings = _connectionStrings.Get(Core.Constants.System.UmbracoConnectionName);
|
||||
|
||||
if (databaseSettings.IsConnectionStringConfigured())
|
||||
{
|
||||
try
|
||||
{
|
||||
//Since a connection string was present we verify the db can connect and query
|
||||
_ = _databaseBuilder.ValidateSchema();
|
||||
// Since a connection string was present we verify the db can connect and query
|
||||
_databaseBuilder.ValidateSchema();
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Something went wrong, could not connect so probably need to reconfigure
|
||||
_logger.LogError(ex, "An error occurred, reconfiguring...");
|
||||
//something went wrong, could not connect so probably need to reconfigure
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
@@ -118,10 +113,7 @@ namespace Umbraco.Cms.Infrastructure.Install.InstallSteps
|
||||
{
|
||||
get
|
||||
{
|
||||
var quickInstallSettings = _databaseProviderMetadata
|
||||
.Where(x => x.SupportsQuickInstall)
|
||||
.Where(x => x.IsAvailable)
|
||||
.OrderBy(x => x.SortOrder)
|
||||
var quickInstallSettings = _databaseProviderMetadata.GetAvailable(true)
|
||||
.Select(x => new
|
||||
{
|
||||
displayName = x.DisplayName,
|
||||
|
||||
@@ -69,10 +69,10 @@ namespace Umbraco.Cms.Infrastructure.Install
|
||||
break;
|
||||
case RuntimeLevelReason.UpgradePackageMigrations:
|
||||
{
|
||||
if (!_runtimeState.StartupState.TryGetValue(RuntimeState.PendingPacakgeMigrationsStateKey, out var pm)
|
||||
if (!_runtimeState.StartupState.TryGetValue(RuntimeState.PendingPackageMigrationsStateKey, out var pm)
|
||||
|| pm is not IReadOnlyList<string> pendingMigrations)
|
||||
{
|
||||
throw new InvalidOperationException($"The required key {RuntimeState.PendingPacakgeMigrationsStateKey} does not exist in startup state");
|
||||
throw new InvalidOperationException($"The required key {RuntimeState.PendingPackageMigrationsStateKey} does not exist in startup state");
|
||||
}
|
||||
|
||||
if (pendingMigrations.Count == 0)
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core;
|
||||
@@ -139,20 +136,15 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
|
||||
// if the database model is null then we will attempt quick install.
|
||||
if (databaseSettings == null)
|
||||
{
|
||||
providerMeta = _databaseProviderMetadata
|
||||
.OrderBy(x => x.SortOrder)
|
||||
.Where(x => x.SupportsQuickInstall)
|
||||
.FirstOrDefault(x => x.IsAvailable);
|
||||
|
||||
providerMeta = _databaseProviderMetadata.GetAvailable(true).FirstOrDefault();
|
||||
databaseSettings = new DatabaseModel
|
||||
{
|
||||
DatabaseName = providerMeta?.DefaultDatabaseName!,
|
||||
DatabaseName = providerMeta?.DefaultDatabaseName!
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
providerMeta = _databaseProviderMetadata
|
||||
.FirstOrDefault(x => x.Id == databaseSettings.DatabaseProviderMetadataId);
|
||||
providerMeta = _databaseProviderMetadata.FirstOrDefault(x => x.Id == databaseSettings.DatabaseProviderMetadataId);
|
||||
}
|
||||
|
||||
if (providerMeta == null)
|
||||
@@ -177,7 +169,6 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void Configure(string connectionString, string? providerName, bool installMissingDatabase)
|
||||
{
|
||||
// Update existing connection string
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
using Umbraco.Cms.Core.Install.Models;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Persistence;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="IDatabaseProviderMetadata" />.
|
||||
/// </summary>
|
||||
public static class DatabaseProviderMetadataExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the available database provider metadata.
|
||||
/// </summary>
|
||||
/// <param name="databaseProviderMetadata">The database provider metadata.</param>
|
||||
/// <param name="onlyQuickInstall">If set to <c>true</c> only returns providers that support quick install.</param>
|
||||
/// <returns>
|
||||
/// The available database provider metadata.
|
||||
/// </returns>
|
||||
public static IEnumerable<IDatabaseProviderMetadata> GetAvailable(this IEnumerable<IDatabaseProviderMetadata> databaseProviderMetadata, bool onlyQuickInstall = false)
|
||||
=> databaseProviderMetadata.Where(x => (!onlyQuickInstall || x.SupportsQuickInstall) && x.IsAvailable).OrderBy(x => x.SortOrder);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a database can be created for the specified provider name while ignoring the value of <see cref="GlobalSettings.InstallMissingDatabase" />.
|
||||
/// </summary>
|
||||
/// <param name="databaseProviderMetadata">The database provider metadata.</param>
|
||||
/// <param name="providerName">The name of the provider.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if a database can be created for the specified provider name; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool CanForceCreateDatabase(this IEnumerable<IDatabaseProviderMetadata> databaseProviderMetadata, string? providerName)
|
||||
=> databaseProviderMetadata.FirstOrDefault(x => x.ProviderName == providerName)?.ForceCreateDatabase == true;
|
||||
|
||||
/// <summary>
|
||||
/// Generates the connection string.
|
||||
/// </summary>
|
||||
/// <param name="databaseProviderMetadata">The database provider metadata.</param>
|
||||
/// <param name="databaseName">The name of the database, uses the default database name when <c>null</c>.</param>
|
||||
/// <param name="server">The server.</param>
|
||||
/// <param name="login">The login.</param>
|
||||
/// <param name="password">The password.</param>
|
||||
/// <param name="integratedAuth">Indicates whether integrated authentication should be used (when supported by the provider).</param>
|
||||
/// <returns>
|
||||
/// The generated connection string.
|
||||
/// </returns>
|
||||
public static string? GenerateConnectionString(this IDatabaseProviderMetadata databaseProviderMetadata, string? databaseName = null, string? server = null, string? login = null, string? password = null, bool? integratedAuth = null)
|
||||
=> databaseProviderMetadata.GenerateConnectionString(new DatabaseModel()
|
||||
{
|
||||
DatabaseProviderMetadataId = databaseProviderMetadata.Id,
|
||||
ProviderName = databaseProviderMetadata.ProviderName,
|
||||
DatabaseName = databaseName ?? databaseProviderMetadata.DefaultDatabaseName,
|
||||
Server = server ?? string.Empty,
|
||||
Login = login ?? string.Empty,
|
||||
Password = password ?? string.Empty,
|
||||
IntegratedAuth = integratedAuth == true && databaseProviderMetadata.SupportsIntegratedAuthentication
|
||||
});
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -14,7 +11,6 @@ using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.Migrations.Upgrade;
|
||||
using Umbraco.Cms.Infrastructure.Persistence;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Runtime
|
||||
{
|
||||
@@ -24,7 +20,8 @@ namespace Umbraco.Cms.Infrastructure.Runtime
|
||||
/// </summary>
|
||||
public class RuntimeState : IRuntimeState
|
||||
{
|
||||
internal const string PendingPacakgeMigrationsStateKey = "PendingPackageMigrations";
|
||||
internal const string PendingPackageMigrationsStateKey = "PendingPackageMigrations";
|
||||
|
||||
private readonly IOptions<GlobalSettings> _globalSettings = null!;
|
||||
private readonly IOptions<UnattendedSettings> _unattendedSettings = null!;
|
||||
private readonly IUmbracoVersion _umbracoVersion = null!;
|
||||
@@ -33,6 +30,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime
|
||||
private readonly PendingPackageMigrations _packageMigrationState = null!;
|
||||
private readonly Dictionary<string, object> _startupState = new Dictionary<string, object>();
|
||||
private readonly IConflictingRouteService _conflictingRouteService = null!;
|
||||
private readonly IEnumerable<IDatabaseProviderMetadata> _databaseProviderMetadata = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The initial <see cref="RuntimeState"/>
|
||||
@@ -41,17 +39,17 @@ namespace Umbraco.Cms.Infrastructure.Runtime
|
||||
public static RuntimeState Booting() => new RuntimeState() { Level = RuntimeLevel.Boot };
|
||||
|
||||
private RuntimeState()
|
||||
{
|
||||
}
|
||||
{ }
|
||||
|
||||
public RuntimeState(
|
||||
IOptions<GlobalSettings> globalSettings,
|
||||
IOptions<UnattendedSettings> unattendedSettings,
|
||||
IUmbracoVersion umbracoVersion,
|
||||
IUmbracoDatabaseFactory databaseFactory,
|
||||
ILogger<RuntimeState> logger,
|
||||
PendingPackageMigrations packageMigrationState,
|
||||
IConflictingRouteService conflictingRouteService)
|
||||
IOptions<GlobalSettings> globalSettings,
|
||||
IOptions<UnattendedSettings> unattendedSettings,
|
||||
IUmbracoVersion umbracoVersion,
|
||||
IUmbracoDatabaseFactory databaseFactory,
|
||||
ILogger<RuntimeState> logger,
|
||||
PendingPackageMigrations packageMigrationState,
|
||||
IConflictingRouteService conflictingRouteService,
|
||||
IEnumerable<IDatabaseProviderMetadata> databaseProviderMetadata)
|
||||
{
|
||||
_globalSettings = globalSettings;
|
||||
_unattendedSettings = unattendedSettings;
|
||||
@@ -60,8 +58,29 @@ namespace Umbraco.Cms.Infrastructure.Runtime
|
||||
_logger = logger;
|
||||
_packageMigrationState = packageMigrationState;
|
||||
_conflictingRouteService = conflictingRouteService;
|
||||
_databaseProviderMetadata = databaseProviderMetadata;
|
||||
}
|
||||
|
||||
[Obsolete("Use ctor with all params. This will be removed in Umbraco 12")]
|
||||
public RuntimeState(
|
||||
IOptions<GlobalSettings> globalSettings,
|
||||
IOptions<UnattendedSettings> unattendedSettings,
|
||||
IUmbracoVersion umbracoVersion,
|
||||
IUmbracoDatabaseFactory databaseFactory,
|
||||
ILogger<RuntimeState> logger,
|
||||
PendingPackageMigrations packageMigrationState,
|
||||
IConflictingRouteService conflictingRouteService)
|
||||
: this(
|
||||
globalSettings,
|
||||
unattendedSettings,
|
||||
umbracoVersion,
|
||||
databaseFactory,
|
||||
logger,
|
||||
packageMigrationState,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IConflictingRouteService>(),
|
||||
StaticServiceProvider.Instance.GetServices<IDatabaseProviderMetadata>())
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RuntimeState"/> class.
|
||||
/// </summary>
|
||||
@@ -81,8 +100,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime
|
||||
logger,
|
||||
packageMigrationState,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IConflictingRouteService>())
|
||||
{
|
||||
}
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Version Version => _umbracoVersion.Version;
|
||||
@@ -143,7 +161,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime
|
||||
// cannot connect to configured database, this is bad, fail
|
||||
_logger.LogDebug("Could not connect to database.");
|
||||
|
||||
if (_globalSettings.Value.InstallMissingDatabase || CanAutoInstallMissingDatabase(_databaseFactory))
|
||||
if (_globalSettings.Value.InstallMissingDatabase || _databaseProviderMetadata.CanForceCreateDatabase(_databaseFactory.ProviderName))
|
||||
{
|
||||
// ok to install on a configured but missing database
|
||||
Level = RuntimeLevel.Install;
|
||||
@@ -257,7 +275,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime
|
||||
IReadOnlyList<string> packagesRequiringMigration = _packageMigrationState.GetPendingPackageMigrations(keyValues);
|
||||
if (packagesRequiringMigration.Count > 0)
|
||||
{
|
||||
_startupState[PendingPacakgeMigrationsStateKey] = packagesRequiringMigration;
|
||||
_startupState[PendingPackageMigrationsStateKey] = packagesRequiringMigration;
|
||||
|
||||
return UmbracoDatabaseState.NeedsPackageMigration;
|
||||
}
|
||||
@@ -311,8 +329,5 @@ namespace Umbraco.Cms.Infrastructure.Runtime
|
||||
|
||||
return canConnect;
|
||||
}
|
||||
|
||||
private bool CanAutoInstallMissingDatabase(IUmbracoDatabaseFactory databaseFactory)
|
||||
=> databaseFactory.ConnectionString?.InvariantContains("(localdb)") == true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user