mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 07:36:14 -05:00
move migrations to migrator project
This commit is contained in:
@ -9,6 +9,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Core\Core.csproj" />
|
||||
<ProjectReference Include="..\Migrator\Migrator.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
66
src/Admin/HostedServices/DatabaseMigrationHostedService.cs
Normal file
66
src/Admin/HostedServices/DatabaseMigrationHostedService.cs
Normal file
@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Data.SqlClient;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Jobs;
|
||||
using Bit.Migrator;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.Admin.HostedServices
|
||||
{
|
||||
public class DatabaseMigrationHostedService : IHostedService, IDisposable
|
||||
{
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly ILogger<DatabaseMigrationHostedService> _logger;
|
||||
private readonly DbMigrator _dbMigrator;
|
||||
|
||||
public DatabaseMigrationHostedService(
|
||||
GlobalSettings globalSettings,
|
||||
ILogger<DatabaseMigrationHostedService> logger,
|
||||
ILogger<DbMigrator> migratorLogger,
|
||||
ILogger<JobListener> listenerLogger)
|
||||
{
|
||||
_globalSettings = globalSettings;
|
||||
_logger = logger;
|
||||
_dbMigrator = new DbMigrator(globalSettings.SqlServer.ConnectionString, migratorLogger);
|
||||
}
|
||||
|
||||
public virtual async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var maxMigrationAttempts = 10;
|
||||
for(var i = 1; i <= maxMigrationAttempts; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
_dbMigrator.MigrateMsSqlDatabase(true, cancellationToken);
|
||||
// TODO: Maybe flip a flag somewhere to indicate migration is complete??
|
||||
break;
|
||||
}
|
||||
catch(SqlException e)
|
||||
{
|
||||
if(i >= maxMigrationAttempts)
|
||||
{
|
||||
_logger.LogError(e, "Database failed to migrate.");
|
||||
throw e;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError(e,
|
||||
"Database unavailable for migration. Trying again (attempt #{0})...", i + 1);
|
||||
await Task.Delay(20000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{ }
|
||||
}
|
||||
}
|
@ -75,7 +75,11 @@ namespace Bit.Admin
|
||||
// Jobs service
|
||||
Jobs.JobsHostedService.AddJobsServices(services);
|
||||
services.AddHostedService<Jobs.JobsHostedService>();
|
||||
if(!globalSettings.SelfHosted)
|
||||
if(globalSettings.SelfHosted)
|
||||
{
|
||||
services.AddHostedService<HostedServices.DatabaseMigrationHostedService>();
|
||||
}
|
||||
else
|
||||
{
|
||||
if(CoreHelpers.SettingHasValue(globalSettings.Storage.ConnectionString))
|
||||
{
|
||||
|
@ -1,69 +1,97 @@
|
||||
using System;
|
||||
using System.Data.SqlClient;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using DbUp;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.Migrator
|
||||
{
|
||||
public static class DbMigrator
|
||||
public class DbMigrator
|
||||
{
|
||||
private static void MigrateMsSqlDatabase(string connectionString, int attempt = 1)
|
||||
private readonly string _connectionString;
|
||||
private readonly ILogger<DbMigrator> _logger;
|
||||
private readonly string _masterConnectionString;
|
||||
|
||||
public DbMigrator(string connectionString, ILogger<DbMigrator> logger)
|
||||
{
|
||||
var masterConnectionString = new SqlConnectionStringBuilder(connectionString)
|
||||
_connectionString = connectionString;
|
||||
_logger = logger;
|
||||
_masterConnectionString = new SqlConnectionStringBuilder(connectionString)
|
||||
{
|
||||
InitialCatalog = "master"
|
||||
}.ConnectionString;
|
||||
}
|
||||
|
||||
try
|
||||
public bool MigrateMsSqlDatabase(bool enableLogging = true,
|
||||
CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
if(enableLogging && _logger != null)
|
||||
{
|
||||
using(var connection = new SqlConnection(masterConnectionString))
|
||||
{
|
||||
var command = new SqlCommand(
|
||||
"IF ((SELECT COUNT(1) FROM sys.databases WHERE [name] = 'vault') = 0) " +
|
||||
"CREATE DATABASE [vault];", connection);
|
||||
command.Connection.Open();
|
||||
command.ExecuteNonQuery();
|
||||
command.CommandText = "IF ((SELECT DATABASEPROPERTYEX([name], 'IsAutoClose') " +
|
||||
"FROM sys.databases WHERE [name] = 'vault') = 1) " +
|
||||
"ALTER DATABASE [vault] SET AUTO_CLOSE OFF;";
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
_logger.LogInformation("Migrating database.");
|
||||
}
|
||||
|
||||
var builder = DeployChanges.To
|
||||
.SqlDatabase(connectionString)
|
||||
.JournalToSqlTable("dbo", "Migration")
|
||||
.WithScriptsAndCodeEmbeddedInAssembly(Assembly.GetExecutingAssembly(),
|
||||
s => s.Contains($".DbScripts.") && !s.Contains(".Archive."))
|
||||
.WithTransaction()
|
||||
.WithExecutionTimeout(new TimeSpan(0, 5, 0))
|
||||
.LogToConsole();
|
||||
using(var connection = new SqlConnection(_masterConnectionString))
|
||||
{
|
||||
var command = new SqlCommand(
|
||||
"IF ((SELECT COUNT(1) FROM sys.databases WHERE [name] = 'vault') = 0) " +
|
||||
"CREATE DATABASE [vault];", connection);
|
||||
command.Connection.Open();
|
||||
command.ExecuteNonQuery();
|
||||
|
||||
var upgrader = builder.Build();
|
||||
var result = upgrader.PerformUpgrade();
|
||||
if(result.Successful)
|
||||
command.CommandText = "IF ((SELECT DATABASEPROPERTYEX([name], 'IsAutoClose') " +
|
||||
"FROM sys.databases WHERE [name] = 'vault') = 1) " +
|
||||
"ALTER DATABASE [vault] SET AUTO_CLOSE OFF;";
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
using(var connection = new SqlConnection(_connectionString))
|
||||
{
|
||||
// Rename old migration scripts to new namespace.
|
||||
var command = new SqlCommand(
|
||||
"IF OBJECT_ID('Migration','U') IS NOT NULL " +
|
||||
"UPDATE [dbo].[Migration] SET " +
|
||||
"[ScriptName] = REPLACE([ScriptName], '.Setup.', '.Migrator.');", connection);
|
||||
command.Connection.Open();
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
var builder = DeployChanges.To
|
||||
.SqlDatabase(_connectionString)
|
||||
.JournalToSqlTable("dbo", "Migration")
|
||||
.WithScriptsAndCodeEmbeddedInAssembly(Assembly.GetExecutingAssembly(),
|
||||
s => s.Contains($".DbScripts.") && !s.Contains(".Archive."))
|
||||
.WithTransaction()
|
||||
.WithExecutionTimeout(new TimeSpan(0, 5, 0));
|
||||
|
||||
if(enableLogging)
|
||||
{
|
||||
if(_logger != null)
|
||||
{
|
||||
Console.WriteLine("Migration successful.");
|
||||
builder.LogTo(new DbUpLogger(_logger));
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Migration failed.");
|
||||
builder.LogToConsole();
|
||||
}
|
||||
}
|
||||
catch(SqlException e)
|
||||
|
||||
var upgrader = builder.Build();
|
||||
var result = upgrader.PerformUpgrade();
|
||||
|
||||
if(enableLogging && _logger != null)
|
||||
{
|
||||
if(e.Message.Contains("Server is in script upgrade mode") && attempt < 10)
|
||||
if(result.Successful)
|
||||
{
|
||||
var nextAttempt = attempt + 1;
|
||||
Console.WriteLine("Database is in script upgrade mode. " +
|
||||
"Trying again (attempt #{0})...", nextAttempt);
|
||||
System.Threading.Thread.Sleep(20000);
|
||||
MigrateMsSqlDatabase(connectionString, nextAttempt);
|
||||
return;
|
||||
_logger.LogInformation("Migration successful.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError(result.Error, "Migration failed.");
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return result.Successful;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
30
src/Migrator/DbUpLogger.cs
Normal file
30
src/Migrator/DbUpLogger.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using DbUp.Engine.Output;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.Migrator
|
||||
{
|
||||
public class DbUpLogger : IUpgradeLog
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public DbUpLogger(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void WriteError(string format, params object[] args)
|
||||
{
|
||||
_logger.LogError(format, args);
|
||||
}
|
||||
|
||||
public void WriteInformation(string format, params object[] args)
|
||||
{
|
||||
_logger.LogInformation(format, args);
|
||||
}
|
||||
|
||||
public void WriteWarning(string format, params object[] args)
|
||||
{
|
||||
_logger.LogWarning(format, args);
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="dbup-sqlserver" Version="4.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
Reference in New Issue
Block a user