1
0
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:
Kyle Spearrin
2019-03-25 13:21:05 -04:00
parent cfe3708509
commit 28884c3330
34 changed files with 184 additions and 11566 deletions

View File

@ -9,6 +9,7 @@
<ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" />
<ProjectReference Include="..\Migrator\Migrator.csproj" />
</ItemGroup>
<ItemGroup>

View 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()
{ }
}
}

View File

@ -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))
{

View File

@ -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;
}
}
}

View 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);
}
}
}

View File

@ -11,6 +11,7 @@
<ItemGroup>
<PackageReference Include="dbup-sqlserver" Version="4.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
</ItemGroup>
</Project>