From 6998debc0133852c1b524ed1d23fbfa80f4b80ed Mon Sep 17 00:00:00 2001 From: Kit Roed Date: Mon, 1 Apr 2024 09:29:13 -0500 Subject: [PATCH] Implement repository path command line argument (#454) (#481) * Implement repository path command line argument (#454) RepositoryPath created as an optional parameter which is null by default. Note that this is technically a partial implementation as the ScriptsRunErrorsTable also has a repository_path field which will remain unused, but this seems like a good start. * Simplify repository_path handling * Use repositoryPath from Config instead of passing as separate parameter * Insert in ScriptsRunErrors too * Wrote tests on the functionality --------- Co-authored-by: Erik A. Brandstadmoen --- docs/ConfigurationOptions/index.md | 1 + .../Configuration/GrateConfiguration.cs | 5 +++ .../GrateConfigurationBuilder.cs | 12 ++++++ .../Infrastructure/TokenProvider.cs | 2 +- src/grate.core/Migration/AnsiSqlDatabase.cs | 22 +++++++--- src/grate.oracle/Migration/OracleDatabase.cs | 17 ++++++-- src/grate/Commands/MigrateCommand.cs | 7 ++++ .../Basic_CommandLineParsing.cs | 16 +++++++ .../Infrastructure/MockDbMigrator.cs | 2 +- .../Failing_Scripts.cs | 42 +++++++++++++++++++ .../Versioning_The_Database.cs | 38 +++++++++++++++++ 11 files changed, 153 insertions(+), 11 deletions(-) diff --git a/docs/ConfigurationOptions/index.md b/docs/ConfigurationOptions/index.md index ae46e7d0..3205b1eb 100644 --- a/docs/ConfigurationOptions/index.md +++ b/docs/ConfigurationOptions/index.md @@ -43,6 +43,7 @@ grate --connectionstring="Server=(localdb)\MSSQLLocalDB;Integrated Security=true | --dryrun | false | **DryRun** - This instructs grate to log what would have run, but not to actually run anything against the database. Use this option if you are trying to figure out what grate is going to do. | | --restore | - | **Restore** - This instructs grate where to find the database backup file (.bak) to restore from. If this option is not specified, no restore will be done. | -ni
--noninteractive
--silent | false | **Silent** - tells grate not to ask for any input when it runs. +| -r
--repo
--repositorypath | - | **Repository Path** - RepositoryPath - The repository. A string that can be anything. Used to track versioning along with the version. Defaults to `null`. | | --version | 1.0.0.0 | **Database Version** - specify the version of the current migration directly on the command line. | | -v
--verbosity <Critical\|
Debug\|
Error\|
Information\|
None\|
Trace\|Warning> | Information | **Verbosity level** (as defined here: https://docs.microsoft.com/dotnet/api/Microsoft.Extensions.Logging.LogLevel) | -?
-h
--help | - | Show help and usage information | diff --git a/src/grate.core/Configuration/GrateConfiguration.cs b/src/grate.core/Configuration/GrateConfiguration.cs index 7cd967dd..26dfa763 100644 --- a/src/grate.core/Configuration/GrateConfiguration.cs +++ b/src/grate.core/Configuration/GrateConfiguration.cs @@ -78,6 +78,11 @@ public record GrateConfiguration /// public GrateEnvironment? Environment { get; init; } + /// + /// The optional repository path value used to track along with version. + /// + public string? RepositoryPath { get; init; } + /// /// The database version we're migrating to on this run. /// diff --git a/src/grate.core/Configuration/GrateConfigurationBuilder.cs b/src/grate.core/Configuration/GrateConfigurationBuilder.cs index 36645d8c..87de162f 100644 --- a/src/grate.core/Configuration/GrateConfigurationBuilder.cs +++ b/src/grate.core/Configuration/GrateConfigurationBuilder.cs @@ -116,6 +116,18 @@ public GrateConfigurationBuilder WithAdminConnectionString(string adminConnectio return this; } + /// + /// Repository path of service to use. Grate will store the repository path in the database + /// for history and tracking purposes. + /// + /// service migration repository path + /// GrateConfigurationBuilder + public GrateConfigurationBuilder WithRepositoryPath(string repositoryPath) + { + _grateConfiguration = _grateConfiguration with { RepositoryPath = repositoryPath }; + return this; + } + /// /// Version of service to use. Grate will store the version in the database /// for history and tracking purposes. diff --git a/src/grate.core/Infrastructure/TokenProvider.cs b/src/grate.core/Infrastructure/TokenProvider.cs index 64a95232..a00ab2ba 100644 --- a/src/grate.core/Infrastructure/TokenProvider.cs +++ b/src/grate.core/Infrastructure/TokenProvider.cs @@ -57,7 +57,7 @@ public TokenProvider(GrateConfiguration config, IDatabase db) ["OutputPath"] = _config.OutputPath.FullName, //TODO: Does RH use name or full path? ["PermissionsFolderName"] = GetFolder(KnownFolderKeys.Permissions).ToToken(), //["RecoveryMode"] = RecoveryMode.to_string(), - //["RepositoryPath"] = RepositoryPath.to_string(), + ["RepositoryPath"] = _config.RepositoryPath, ["Restore"] = _config.Restore, //["RestoreTimeout"] = RestoreTimeout.to_string(), ["RunAfterCreateDatabaseFolderName"] = GetFolder(KnownFolderKeys.RunAfterCreateDatabase).ToToken(), diff --git a/src/grate.core/Migration/AnsiSqlDatabase.cs b/src/grate.core/Migration/AnsiSqlDatabase.cs index 0b35fe3e..00b08f48 100644 --- a/src/grate.core/Migration/AnsiSqlDatabase.cs +++ b/src/grate.core/Migration/AnsiSqlDatabase.cs @@ -324,11 +324,13 @@ public virtual async Task VersionTheDatabase(string newVersion) { var sql = Parameterize($@" INSERT INTO {VersionTable} -(version, entry_date, modified_date, entered_by, status) -VALUES(@newVersion, @entryDate, @modifiedDate, @enteredBy, @status) +(repository_path, version, entry_date, modified_date, entered_by, status) +VALUES(@repositoryPath, @newVersion, @entryDate, @modifiedDate, @enteredBy, @status) {_syntax.ReturnId} "); + var repositoryPath = Config?.RepositoryPath; + long versionId; try @@ -337,6 +339,7 @@ INSERT INTO {VersionTable} sql, new { + repositoryPath, newVersion, entryDate = DateTime.UtcNow, modifiedDate = DateTime.UtcNow, @@ -350,7 +353,14 @@ INSERT INTO {VersionTable} versionId = 1; } - Logger.LogInformation(" Versioning {DbName} database with version {Version}.", DatabaseName, newVersion); + if (repositoryPath != null) + { + Logger.LogInformation(" Versioning {DbName} database with version {Version} based on {RepositoryPath}.", DatabaseName, newVersion, repositoryPath); + } + else + { + Logger.LogInformation(" Versioning {DbName} database with version {Version}.", DatabaseName, newVersion); + } return versionId; } @@ -538,14 +548,16 @@ public async Task InsertScriptRunError(string scriptName, string? sql, string er { var insertSql = Parameterize($@" INSERT INTO {ScriptsRunErrorsTable} -(version, script_name, text_of_script, erroneous_part_of_script, error_message, entry_date, modified_date, entered_by) -VALUES (@version, @scriptName, @sql, @errorSql, @errorMessage, @now, @now, @usr)"); +(repository_path, version, script_name, text_of_script, erroneous_part_of_script, error_message, entry_date, modified_date, entered_by) +VALUES (@repositoryPath, @version, @scriptName, @sql, @errorSql, @errorMessage, @now, @now, @usr)"); var versionSql = Parameterize($"SELECT version FROM {VersionTable} WHERE id = @versionId"); var version = await ExecuteScalarAsync(ActiveConnection, versionSql, new { versionId }); + var repositoryPath = Config?.RepositoryPath; var scriptRunErrors = new DynamicParameters(); + scriptRunErrors.Add(nameof(repositoryPath), repositoryPath); scriptRunErrors.Add(nameof(version), version); scriptRunErrors.Add(nameof(scriptName), scriptName); scriptRunErrors.Add(nameof(sql), sql, DbType.String); diff --git a/src/grate.oracle/Migration/OracleDatabase.cs b/src/grate.oracle/Migration/OracleDatabase.cs index ef078f70..1db6422a 100644 --- a/src/grate.oracle/Migration/OracleDatabase.cs +++ b/src/grate.oracle/Migration/OracleDatabase.cs @@ -97,12 +97,15 @@ public override async Task VersionTheDatabase(string newVersion) { var sql = (string)$@" INSERT INTO {VersionTable} -(version, entry_date, modified_date, entered_by, status) -VALUES(:newVersion, :entryDate, :modifiedDate, :enteredBy, :status) +(repository_path, version, entry_date, modified_date, entered_by, status) +VALUES(:repositoryPath, :newVersion, :entryDate, :modifiedDate, :enteredBy, :status) RETURNING id into :id "; + var repositoryPath = Config?.RepositoryPath; + var parameters = new { + repositoryPath, newVersion, entryDate = DateTime.UtcNow, modifiedDate = DateTime.UtcNow, @@ -125,8 +128,14 @@ INSERT INTO {VersionTable} versionId = 1; } - - Logger.LogInformation(" Versioning {DbName} database with version {Version}.", DatabaseName, newVersion); + if (repositoryPath != null) + { + Logger.LogInformation(" Versioning {DbName} database with version {Version} based on {RepositoryPath}.", DatabaseName, newVersion, repositoryPath); + } + else + { + Logger.LogInformation(" Versioning {DbName} database with version {Version}.", DatabaseName, newVersion); + } return versionId; } diff --git a/src/grate/Commands/MigrateCommand.cs b/src/grate/Commands/MigrateCommand.cs index 53c28ec8..a632a170 100644 --- a/src/grate/Commands/MigrateCommand.cs +++ b/src/grate/Commands/MigrateCommand.cs @@ -26,6 +26,7 @@ public MigrateCommand(IGrateMigrator mi) : base("Migrates the database") Add(Environment()); Add(SchemaName()); Add(Silent()); + Add(RepositoryPath()); Add(Version()); Add(Drop()); Add(CreateDatabase()); @@ -281,6 +282,12 @@ private static Option Silent() => "Silent - tells grate not to ask for any input when it runs." ); + private static Option RepositoryPath() => + new( + new[] { "-r", "--repo", "--repositorypath" }, + "Repository Path - The repository. A string that can be anything. Used to track versioning along with the version. Defaults to NULL." + ); + private static Option Version() => new( new[] { "--version" }, diff --git a/unittests/Basic_tests/CommandLineParsing/Basic_CommandLineParsing.cs b/unittests/Basic_tests/CommandLineParsing/Basic_CommandLineParsing.cs index 9b73974e..65713eca 100644 --- a/unittests/Basic_tests/CommandLineParsing/Basic_CommandLineParsing.cs +++ b/unittests/Basic_tests/CommandLineParsing/Basic_CommandLineParsing.cs @@ -91,6 +91,22 @@ public async Task OutputPath(string argName) cfg?.OutputPath.ToString().Should().Be(database); } + [Theory] + [InlineData("-r ")] + [InlineData("-r=")] + [InlineData("--repo ")] + [InlineData("--repo=")] + [InlineData("--repositorypath ")] + [InlineData("--repositorypath=")] + public async Task RepositoryPath(string argName) + { + var repositorypath = "git@example.com:user/repo.git"; + var commandline = argName + repositorypath; + var cfg = await ParseGrateConfiguration(commandline); + + cfg?.RepositoryPath.Should().Be(repositorypath); + } + [Theory] [InlineData("--version=")] [InlineData("--version ")] diff --git a/unittests/Basic_tests/Infrastructure/MockDbMigrator.cs b/unittests/Basic_tests/Infrastructure/MockDbMigrator.cs index 1c2b44b1..63990837 100644 --- a/unittests/Basic_tests/Infrastructure/MockDbMigrator.cs +++ b/unittests/Basic_tests/Infrastructure/MockDbMigrator.cs @@ -1,4 +1,4 @@ -using grate.Configuration; +using grate.Configuration; using grate.Infrastructure; using grate.Migration; using grate.Sqlite.Migration; diff --git a/unittests/TestCommon/Generic/Running_MigrationScripts/Failing_Scripts.cs b/unittests/TestCommon/Generic/Running_MigrationScripts/Failing_Scripts.cs index 5115c3a3..ae94a3c3 100644 --- a/unittests/TestCommon/Generic/Running_MigrationScripts/Failing_Scripts.cs +++ b/unittests/TestCommon/Generic/Running_MigrationScripts/Failing_Scripts.cs @@ -143,6 +143,48 @@ public async Task Inserts_Failed_Scripts_Into_ScriptRunErrors_Table() scripts.Should().HaveCount(1); } + + [Fact] + public async Task Inserts_RepositoryPath_Into_ScriptRunErrors_Table() + { + var db = TestConfig.RandomDatabase(); + + var parent = CreateRandomTempDirectory(); + var knownFolders = FoldersConfiguration.Default(null); + CreateInvalidSql(parent, knownFolders[Up]); + + var repositoryPath = "https://github.com/blah/blah.git"; + + var config = GrateConfigurationBuilder.Create(Context.DefaultConfiguration) + .WithConnectionString(Context.ConnectionString(db)) + .WithFolders(knownFolders) + .WithSqlFilesDirectory(parent) + .WithRepositoryPath(repositoryPath) + .Build(); + + await using (var migrator = Context.Migrator.WithConfiguration(config)) + { + try + { + await migrator.Migrate(); + } + catch (MigrationFailed) + { + } + } + + string? loggedRepositoryPath; + string sql = $"SELECT repository_path FROM {Context.Syntax.TableWithSchema("grate", "ScriptsRunErrors")}"; + + using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled)) + { + using var conn = Context.CreateDbConnection(db); + loggedRepositoryPath = (await conn.QuerySingleOrDefaultAsync(sql)); + } + + loggedRepositoryPath.Should().Be(repositoryPath); + } + [Fact] public async Task Inserts_Large_Failed_Scripts_Into_ScriptRunErrors_Table() diff --git a/unittests/TestCommon/Generic/Running_MigrationScripts/Versioning_The_Database.cs b/unittests/TestCommon/Generic/Running_MigrationScripts/Versioning_The_Database.cs index 8c9e38d2..36dc7aff 100644 --- a/unittests/TestCommon/Generic/Running_MigrationScripts/Versioning_The_Database.cs +++ b/unittests/TestCommon/Generic/Running_MigrationScripts/Versioning_The_Database.cs @@ -113,6 +113,44 @@ public async Task Creates_a_new_version_with_status_InProgress() var version = entries.Single(x => x.version == dbVersion); version.status.Should().Be(MigrationStatus.InProgress); } + + [Fact] + [Trait("Category", "Versioning")] + public async Task Includes_RepositoryPath_in_version_table() + { + var db = TestConfig.RandomDatabase(); + var dbVersion = "1.2.3.4"; + + var parent = CreateRandomTempDirectory(); + var knownFolders = FoldersConfiguration.Default(null); + CreateDummySql(parent, knownFolders[Up]); + + var repositoryPath = "any repository path"; + var config = GrateConfigurationBuilder.Create(Context.DefaultConfiguration) + .WithConnectionString(Context.ConnectionString(db)) + .WithFolders(knownFolders) + .WithSqlFilesDirectory(parent) + .WithRepositoryPath(repositoryPath) + .WithVersion(dbVersion) + .Build(); + + await using (var migrator = Context.Migrator.WithConfiguration(config)) + { + //Calling migrate here to setup the database and such. + await migrator.Migrate(); + } + + string? loggedRepositoryPath; + string sql = $"SELECT repository_path FROM {Context.Syntax.TableWithSchema("grate", "Version")}"; + + using (var conn = Context.CreateDbConnection(db)) + { + loggedRepositoryPath = await conn.QuerySingleOrDefaultAsync(sql); + } + + loggedRepositoryPath.Should().Be(repositoryPath); + } + [Fact] [Trait("Category", "Versioning")]