Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DEVOPS-1537 & DEVOPS-1519] Update dbo.Migrations table to support own migration schema #3212

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
d83554b
Add RerunableSqlTableJournal
michalchecinski Aug 16, 2023
0d67ee7
Add extension to use rerunable sql table journal
michalchecinski Aug 16, 2023
4949c62
Use rerunable sql journal
michalchecinski Aug 16, 2023
e47d544
format
michalchecinski Aug 17, 2023
7a7585e
Enable logging
michalchecinski Aug 17, 2023
068c73b
FIx
michalchecinski Aug 18, 2023
1968d11
Disable logging
michalchecinski Aug 18, 2023
2c3c869
Rename to SqlTableJournalExtensions
michalchecinski Aug 21, 2023
683fd71
Move RerunableSqlTableJournal to Extension class
michalchecinski Aug 21, 2023
ee26db7
Fix usings
michalchecinski Aug 21, 2023
ff021b3
Add rerunable schema
michalchecinski Aug 23, 2023
47f8555
Format
michalchecinski Aug 23, 2023
c606efe
Fix typo
michalchecinski Aug 24, 2023
edc8139
Enable logging in db migrator
michalchecinski Aug 24, 2023
d413b4b
add rerunable column in dbo migrations table migration
michalchecinski Aug 24, 2023
98168fe
Trying
michalchecinski Aug 24, 2023
d73dbfb
Fix journal table name
michalchecinski Aug 24, 2023
a742316
Trying to migrate first
michalchecinski Aug 24, 2023
d95c766
After migration
michalchecinski Aug 24, 2023
b5420e1
Testing
michalchecinski Aug 25, 2023
731be1f
Add update from rerunable to not rerunable script
michalchecinski Aug 25, 2023
3d65219
Change name
michalchecinski Aug 25, 2023
d82025b
Add rerunable option and script folder name
michalchecinski Aug 28, 2023
5ecffda
Add rerunable options and folder
michalchecinski Aug 28, 2023
5d7da92
Fix
michalchecinski Aug 29, 2023
d242e6e
Add transition (aka rerunable) migrations to Setup
michalchecinski Aug 31, 2023
c6df753
Parse parameters on migrator utility
michalchecinski Aug 31, 2023
82c1742
Fix sql scripts
michalchecinski Aug 31, 2023
928179e
Remove CreateSchemaTableSql as it'll be migrated using migration
michalchecinski Aug 31, 2023
8dab294
Embed dbScripts_data_migration folder
michalchecinski Sep 1, 2023
50145fb
Remove testing sql script
michalchecinski Sep 1, 2023
3bbf36e
Add optins parsing nuget for msSqlMigratorUtility
michalchecinski Sep 1, 2023
953e645
Fix sql journal
michalchecinski Sep 1, 2023
c3f58e6
Ran dotnet format
michalchecinski Sep 4, 2023
df15fa9
Comment out index
michalchecinski Sep 5, 2023
00056cb
▫️Revert "Comment out index"
michalchecinski Sep 5, 2023
9ba7b94
Merge remote-tracking branch 'origin/master' into DEVOPS-1537-Update-…
michalchecinski Sep 5, 2023
54e92b5
Disable logging
michalchecinski Sep 5, 2023
3682633
Add newline
michalchecinski Sep 6, 2023
95c14e4
Rename rerunable to repeatable
michalchecinski Sep 11, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions util/Migrator/DbMigrator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public DbMigrator(string connectionString, ILogger<DbMigrator> logger)
}

public bool MigrateMsSqlDatabaseWithRetries(bool enableLogging = true,
bool repeatable = false,
string folderName = "DbScripts",
CancellationToken cancellationToken = default(CancellationToken))
{
var attempt = 1;
Expand All @@ -32,7 +34,7 @@ public bool MigrateMsSqlDatabaseWithRetries(bool enableLogging = true,
{
try
{
var success = MigrateDatabase(enableLogging, cancellationToken);
var success = MigrateDatabase(enableLogging, repeatable, folderName, cancellationToken);
return success;
}
catch (SqlException ex)
Expand All @@ -54,6 +56,8 @@ public bool MigrateMsSqlDatabaseWithRetries(bool enableLogging = true,
}

public bool MigrateDatabase(bool enableLogging = true,
bool repeatable = false,
string folderName = "DbScripts",
CancellationToken cancellationToken = default(CancellationToken))
{
if (_logger != null)
Expand Down Expand Up @@ -98,9 +102,9 @@ public bool MigrateDatabase(bool enableLogging = true,
cancellationToken.ThrowIfCancellationRequested();
var builder = DeployChanges.To
.SqlDatabase(_connectionString)
.JournalToSqlTable("dbo", "Migration")
.JournalRepeatableToSqlTable("dbo", "Migration", repeatable)
.WithScriptsAndCodeEmbeddedInAssembly(Assembly.GetExecutingAssembly(),
s => s.Contains($".DbScripts.") && !s.Contains(".Archive."))
s => s.Contains($".{folderName}.") && !s.Contains(".Archive."))
.WithTransaction()
.WithExecutionTimeout(new TimeSpan(0, 5, 0));

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ALTER TABLE dbo.Migration
ADD Repeatable bit NOT NULL DEFAULT 0
GO
1 change: 1 addition & 0 deletions util/Migrator/Migrator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<ItemGroup>
<EmbeddedResource Include="DbScripts\**\*.sql" />
<EmbeddedResource Include="DbScripts_data_migration\**\*.sql" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Is the _data_migration in docs somewhere, or a recommendation? DbDataMigrationScripts or DbDataMigrations is cleaner to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've got another card to rename this folder: https://bitwarden.atlassian.net/browse/DEVOPS-1520

</ItemGroup>

<ItemGroup>
Expand Down
105 changes: 105 additions & 0 deletions util/Migrator/SqlTableJournalExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using System.Data;
using DbUp.Builder;
using DbUp.Engine;
using DbUp.Engine.Output;
using DbUp.Engine.Transactions;
using DbUp.SqlServer;

namespace Bit.Migrator;

public static class SqlTableJournalExtensions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Did you ever look into my comment about using NullJournal? Now that the implementation is here, what is the purpose of tracking scripts that you always want to run? If you have a folder for some that can always run, that's a use case for a different journal otherwise you'd keep using the one already in place. What am I missing in the requirements?

{
public static UpgradeEngineBuilder JournalRepeatableToSqlTable(this UpgradeEngineBuilder builder, string schema, string table, bool repeatable = false)
{
builder.Configure(c => c.Journal = new RepeatableSqlTableJournal(() => c.ConnectionManager, () => c.Log, schema, table, repeatable));
return builder;
}
}

public class RepeatableSqlTableJournal : SqlTableJournal
{
private bool Repeatable { get; set; }

public RepeatableSqlTableJournal(Func<IConnectionManager> connectionManager, Func<IUpgradeLog> logger, string schema, string table, bool repeatable = false)
: base(connectionManager, logger, schema, table)
{
Repeatable = repeatable;
}

public override void StoreExecutedScript(SqlScript script, Func<IDbCommand> dbCommandFactory)
{
EnsureTableExistsAndIsLatestVersion(dbCommandFactory);
using (var command = GetInsertScriptCommand(dbCommandFactory, script))
{
command.ExecuteNonQuery();
}
}

protected new IDbCommand GetInsertScriptCommand(Func<IDbCommand> dbCommandFactory, SqlScript script)
{
var command = dbCommandFactory();

var scriptNameParam = command.CreateParameter();
scriptNameParam.ParameterName = "scriptName";
scriptNameParam.Value = script.Name;
command.Parameters.Add(scriptNameParam);

var scriptFilename = script.Name.Replace("Bit.Migrator.", "");
scriptFilename = scriptFilename.Substring(scriptFilename.IndexOf('.') + 1);

var scriptFileNameParam = command.CreateParameter();
scriptFileNameParam.ParameterName = "scriptFileName";
scriptFileNameParam.Value = $"%{scriptFilename}";
command.Parameters.Add(scriptFileNameParam);

var appliedParam = command.CreateParameter();
appliedParam.ParameterName = "applied";
appliedParam.Value = DateTime.Now;
command.Parameters.Add(appliedParam);

var repeatableParam = command.CreateParameter();
repeatableParam.ParameterName = "repeatable";
repeatableParam.Value = Repeatable;
command.Parameters.Add(repeatableParam);

command.CommandText = GetInsertJournalEntrySql("@scriptName", "@applied", "@repeatable", "@scriptFileName");
command.CommandType = CommandType.Text;
return command;
}

protected string GetInsertJournalEntrySql(string @scriptName, string @applied, string @repeatable, string @scriptFileName)
{
return @$"IF EXISTS (SELECT * FROM {FqSchemaTableName} WHERE Repeatable = 1 AND ScriptName like {@scriptFileName})
BEGIN
UPDATE {FqSchemaTableName} SET ScriptName = {@scriptName}, Applied = {@applied}, Repeatable = {@repeatable} WHERE ScriptName like {@scriptFileName}
END
ELSE
BEGIN
insert into {FqSchemaTableName} (ScriptName, Applied, Repeatable) values ({@scriptName}, {@applied}, {@repeatable})
END ";
}

protected override string GetJournalEntriesSql()
{
return @$"DECLARE @columnVariable AS NVARCHAR(max)

SELECT @columnVariable =
CASE WHEN EXISTS
(
SELECT 1 FROM sys.columns WHERE Name = N'Repeatable' AND Object_ID = Object_ID(N'dbo.Migration')
)
THEN
(
'where [Repeatable] = 0'
)
ELSE
(
''
)
END

DECLARE @SQLString AS NVARCHAR(max) = N'select [ScriptName] from dbo.Migration ' + @columnVariable + ' order by [ScriptName]'

EXECUTE sp_executesql @SQLString";
}
}
1 change: 1 addition & 0 deletions util/MsSqlMigratorUtility/MsSqlMigratorUtility.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="CommandDotNet" Version="7.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
</ItemGroup>
Expand Down
82 changes: 49 additions & 33 deletions util/MsSqlMigratorUtility/Program.cs
Original file line number Diff line number Diff line change
@@ -1,55 +1,51 @@
using Bit.Migrator;
using CommandDotNet;
using Microsoft.Extensions.Logging;

internal class Program
{
private static IDictionary<string, string> Parameters { get; set; }

private static int Main(string[] args)
{
if (args.Length == 0)
{
Console.WriteLine("Please enter a database connection string argument.");
WriteUsageToConsole();
return 1;
}

if (args.Length == 1 && (args[0] == "--verbose" || args[0] == "-v"))
{
Console.WriteLine($"Please enter a database connection string argument before {args[0]} option.");
WriteUsageToConsole();
return 1;
}

var databaseConnectionString = args[0];

var verbose = false;

if (args.Length == 2 && (args[1] == "--verbose" || args[1] == "-v"))
{
verbose = true;
}

var success = MigrateDatabase(databaseConnectionString, verbose);

if (!success)
{
return -1;
}

return 0;
return new AppRunner<Program>().Run(args);
}

[DefaultCommand]
public void Execute(
[Operand(Description = "Database connection string")]
string databaseConnectionString,
[Option('v', "verbose", Description = "Enable verbose output of migrator logs")]
bool verbose = false,
[Option('r', "repeatable", Description = "Mark scripts as repeatable")]
bool repeatable = false,
[Option('f', "folder", Description = "Folder name of database scripts")]
string folderName = "DbScripts") => MigrateDatabase(databaseConnectionString, verbose, repeatable, folderName);

private static void WriteUsageToConsole()
{
Console.WriteLine("Usage: MsSqlMigratorUtility <database-connection-string>");
Console.WriteLine("Usage: MsSqlMigratorUtility <database-connection-string> -v|--verbose (for verbose output of migrator logs)");
Console.WriteLine("Usage: MsSqlMigratorUtility <database-connection-string> -r|--repeatable (for marking scripts as repeatable) -f|--folder <folder-name-in-migrator-project> (for specifying folder name of scripts)");
Console.WriteLine("Usage: MsSqlMigratorUtility <database-connection-string> -v|--verbose (for verbose output of migrator logs) -r|--repeatable (for marking scripts as repeatable) -f|--folder <folder-name-in-migrator-project> (for specifying folder name of scripts)");
}

private static bool MigrateDatabase(string databaseConnectionString, bool verbose = false, int attempt = 1)
private static bool MigrateDatabase(string databaseConnectionString, bool verbose = false, bool repeatable = false, string folderName = "")
{
Console.WriteLine($"repeatable: {repeatable}");
Console.WriteLine($"folderName: {folderName}");
var logger = CreateLogger(verbose);

var migrator = new DbMigrator(databaseConnectionString, logger);
var success = migrator.MigrateMsSqlDatabaseWithRetries(verbose);
bool success = false;
if (!string.IsNullOrWhiteSpace(folderName))
{
success = migrator.MigrateMsSqlDatabaseWithRetries(verbose, repeatable, folderName);
}
else
{
success = migrator.MigrateMsSqlDatabaseWithRetries(verbose, repeatable);
}

return success;
}
Expand All @@ -75,4 +71,24 @@ private static ILogger<DbMigrator> CreateLogger(bool verbose)
var logger = loggerFactory.CreateLogger<DbMigrator>();
return logger;
}

private static void ParseParameters(string[] args)
{
Parameters = new Dictionary<string, string>();
for (var i = 0; i < args.Length; i += 2)
{
if (!args[i].StartsWith("-"))
{
continue;
}
if (i + 1 <= args.Length && !args[i + 1].StartsWith("-"))
{
Parameters.Add(args[i].Substring(1), args[i + 1]);
}
else
{
Parameters.Add(args[i].Substring(1), null);
}
}
}
}
9 changes: 9 additions & 0 deletions util/MsSqlMigratorUtility/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
"version": 1,
"dependencies": {
"net6.0": {
"CommandDotNet": {
"type": "Direct",
"requested": "[7.0.2, )",
"resolved": "7.0.2",
"contentHash": "sFfqn4T6Ux4AbGnhS+BZvf9iVeP6b9p9bwaMT8nsefVcYH2tC9MHj3AoY+GG0nnBPmFawRqdgud08cBjBdPByQ==",
"dependencies": {
"Microsoft.CSharp": "4.7.0"
}
},
"Microsoft.Extensions.Logging": {
"type": "Direct",
"requested": "[6.0.0, )",
Expand Down
7 changes: 6 additions & 1 deletion util/Setup/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,12 @@ private static void MigrateDatabase(int attempt = 1)
var vaultConnectionString = Helpers.GetValueFromEnvFile("global",
"globalSettings__sqlServer__connectionString");
var migrator = new DbMigrator(vaultConnectionString, null);
migrator.MigrateMsSqlDatabaseWithRetries(false);

var log = false;

migrator.MigrateMsSqlDatabaseWithRetries(log);

migrator.MigrateMsSqlDatabaseWithRetries(log, true, "DbScripts_data_migration");
}

private static bool ValidateInstallation()
Expand Down
Loading