From addec57c64bce81ff1c9812064b04249a302c87f Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 2 Aug 2017 20:06:49 +1000 Subject: [PATCH 1/4] CLI factoring improvements --- piggy.sln | 16 ++++++++-------- src/Datalust.Piggy/Cli/Command.cs | 14 ++++++-------- src/Datalust.Piggy/Cli/CommandFeature.cs | 10 +++------- src/Datalust.Piggy/Cli/CommandLineHost.cs | 2 +- src/Datalust.Piggy/Cli/Commands/UpdateCommand.cs | 4 +--- .../Cli/Features/DatabaseFeature.cs | 10 +++++++++- .../Cli/Features/UsernamePasswordFeature.cs | 10 +++++++++- src/Datalust.Piggy/Cli/Requirement.cs | 15 +++++++++++++++ 8 files changed, 52 insertions(+), 29 deletions(-) create mode 100644 src/Datalust.Piggy/Cli/Requirement.cs diff --git a/piggy.sln b/piggy.sln index c421441..8093edc 100644 --- a/piggy.sln +++ b/piggy.sln @@ -43,20 +43,20 @@ Global GlobalSection(ProjectConfigurationPlatforms) = postSolution {8250E09A-7479-4322-A251-DABFD9F129B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8250E09A-7479-4322-A251-DABFD9F129B6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8250E09A-7479-4322-A251-DABFD9F129B6}.Debug|x64.ActiveCfg = Debug|x64 - {8250E09A-7479-4322-A251-DABFD9F129B6}.Debug|x64.Build.0 = Debug|x64 + {8250E09A-7479-4322-A251-DABFD9F129B6}.Debug|x64.ActiveCfg = Debug|Any CPU + {8250E09A-7479-4322-A251-DABFD9F129B6}.Debug|x64.Build.0 = Debug|Any CPU {8250E09A-7479-4322-A251-DABFD9F129B6}.Release|Any CPU.ActiveCfg = Release|Any CPU {8250E09A-7479-4322-A251-DABFD9F129B6}.Release|Any CPU.Build.0 = Release|Any CPU - {8250E09A-7479-4322-A251-DABFD9F129B6}.Release|x64.ActiveCfg = Release|x64 - {8250E09A-7479-4322-A251-DABFD9F129B6}.Release|x64.Build.0 = Release|x64 + {8250E09A-7479-4322-A251-DABFD9F129B6}.Release|x64.ActiveCfg = Release|Any CPU + {8250E09A-7479-4322-A251-DABFD9F129B6}.Release|x64.Build.0 = Release|Any CPU {E3291CB6-F4A9-4A01-9E65-B70E2A32B0D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E3291CB6-F4A9-4A01-9E65-B70E2A32B0D5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E3291CB6-F4A9-4A01-9E65-B70E2A32B0D5}.Debug|x64.ActiveCfg = Debug|x64 - {E3291CB6-F4A9-4A01-9E65-B70E2A32B0D5}.Debug|x64.Build.0 = Debug|x64 + {E3291CB6-F4A9-4A01-9E65-B70E2A32B0D5}.Debug|x64.ActiveCfg = Debug|Any CPU + {E3291CB6-F4A9-4A01-9E65-B70E2A32B0D5}.Debug|x64.Build.0 = Debug|Any CPU {E3291CB6-F4A9-4A01-9E65-B70E2A32B0D5}.Release|Any CPU.ActiveCfg = Release|Any CPU {E3291CB6-F4A9-4A01-9E65-B70E2A32B0D5}.Release|Any CPU.Build.0 = Release|Any CPU - {E3291CB6-F4A9-4A01-9E65-B70E2A32B0D5}.Release|x64.ActiveCfg = Release|x64 - {E3291CB6-F4A9-4A01-9E65-B70E2A32B0D5}.Release|x64.Build.0 = Release|x64 + {E3291CB6-F4A9-4A01-9E65-B70E2A32B0D5}.Release|x64.ActiveCfg = Release|Any CPU + {E3291CB6-F4A9-4A01-9E65-B70E2A32B0D5}.Release|x64.Build.0 = Release|Any CPU {5E28D963-3523-49DE-B03B-E76684258415}.Debug|Any CPU.ActiveCfg = Debug|x64 {5E28D963-3523-49DE-B03B-E76684258415}.Debug|x64.ActiveCfg = Debug|x64 {5E28D963-3523-49DE-B03B-E76684258415}.Release|Any CPU.ActiveCfg = Release|x64 diff --git a/src/Datalust.Piggy/Cli/Command.cs b/src/Datalust.Piggy/Cli/Command.cs index 69d9332..1e41e01 100644 --- a/src/Datalust.Piggy/Cli/Command.cs +++ b/src/Datalust.Piggy/Cli/Command.cs @@ -6,7 +6,7 @@ namespace Datalust.Piggy.Cli { public abstract class Command { - private readonly IList _features = new List(); + readonly IList _features = new List(); protected Command() { @@ -48,7 +48,7 @@ public int Invoke(string[] args) { var unrecognised = Options.Parse(args).ToArray(); - var errs = _features.SelectMany(f => f.Errors).ToList(); + var errs = _features.SelectMany(f => f.GetUsageErrors()).ToList(); if (errs.Any()) { @@ -67,11 +67,9 @@ public int Invoke(string[] args) protected virtual int Run(string[] unrecognised) { - // All commands used to accept --nologo - var notIgnored = unrecognised.Where(o => o.IndexOf("nologo", StringComparison.OrdinalIgnoreCase) == -1); - if (notIgnored.Any()) + if (unrecognised.Any()) { - ShowUsageErrors(new [] { "Unrecognized options: " + string.Join(", ", notIgnored) }); + ShowUsageErrors(new [] { "Unrecognized options: " + string.Join(", ", unrecognised) }); return -1; } @@ -92,9 +90,9 @@ protected virtual void ShowUsageErrors(IEnumerable errors) protected bool Require(string value, string name) { - if (string.IsNullOrWhiteSpace(value)) + if (!Requirement.IsNonEmpty(value)) { - ShowUsageErrors(new [] { $"Please specify a {name}." }); + ShowUsageErrors(new [] { Requirement.NonEmptyDescription(name) }); return false; } return true; diff --git a/src/Datalust.Piggy/Cli/CommandFeature.cs b/src/Datalust.Piggy/Cli/CommandFeature.cs index 7270980..4297e71 100644 --- a/src/Datalust.Piggy/Cli/CommandFeature.cs +++ b/src/Datalust.Piggy/Cli/CommandFeature.cs @@ -1,16 +1,12 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Datalust.Piggy.Cli { public abstract class CommandFeature { - protected CommandFeature() - { - Errors = new List(); - } - public abstract void Enable(OptionSet options); - public IList Errors { get; private set; } + public virtual IEnumerable GetUsageErrors() => Array.Empty(); } } diff --git a/src/Datalust.Piggy/Cli/CommandLineHost.cs b/src/Datalust.Piggy/Cli/CommandLineHost.cs index 8accbd4..b9ec002 100644 --- a/src/Datalust.Piggy/Cli/CommandLineHost.cs +++ b/src/Datalust.Piggy/Cli/CommandLineHost.cs @@ -31,7 +31,7 @@ public int Run(string[] args) } Console.WriteLine($"Usage: {name} []"); - Console.WriteLine($"Type '{name} help' for available commands."); + Console.WriteLine($"Type '{name} help' for available commands"); return -1; } } diff --git a/src/Datalust.Piggy/Cli/Commands/UpdateCommand.cs b/src/Datalust.Piggy/Cli/Commands/UpdateCommand.cs index a8ccb80..a575920 100644 --- a/src/Datalust.Piggy/Cli/Commands/UpdateCommand.cs +++ b/src/Datalust.Piggy/Cli/Commands/UpdateCommand.cs @@ -38,9 +38,7 @@ protected override int Run() { _loggingFeature.Configure(); - if (!(Require(_databaseFeature.Host, "host") && Require(_databaseFeature.Database, "database") && - Require("username", _usernamePasswordFeature.Username) && Require("password", _usernamePasswordFeature.Password) && - Require(_scriptRoot, "script root directory"))) + if (!Require(_scriptRoot, "script root directory")) return -1; try diff --git a/src/Datalust.Piggy/Cli/Features/DatabaseFeature.cs b/src/Datalust.Piggy/Cli/Features/DatabaseFeature.cs index a294bc4..6c27346 100644 --- a/src/Datalust.Piggy/Cli/Features/DatabaseFeature.cs +++ b/src/Datalust.Piggy/Cli/Features/DatabaseFeature.cs @@ -1,4 +1,6 @@ -namespace Datalust.Piggy.Cli.Features +using System.Collections.Generic; + +namespace Datalust.Piggy.Cli.Features { class DatabaseFeature : CommandFeature { @@ -17,5 +19,11 @@ public override void Enable(OptionSet options) "The database to apply changes to", v => Database = v); } + + public override IEnumerable GetUsageErrors() + { + if (!Requirement.IsNonEmpty(Host)) yield return Requirement.NonEmptyDescription("host"); + if (!Requirement.IsNonEmpty(Database)) yield return Requirement.NonEmptyDescription("database"); + } } } diff --git a/src/Datalust.Piggy/Cli/Features/UsernamePasswordFeature.cs b/src/Datalust.Piggy/Cli/Features/UsernamePasswordFeature.cs index 4b9682b..62b0656 100644 --- a/src/Datalust.Piggy/Cli/Features/UsernamePasswordFeature.cs +++ b/src/Datalust.Piggy/Cli/Features/UsernamePasswordFeature.cs @@ -1,4 +1,6 @@ -namespace Datalust.Piggy.Cli.Features +using System.Collections.Generic; + +namespace Datalust.Piggy.Cli.Features { class UsernamePasswordFeature : CommandFeature { @@ -11,5 +13,11 @@ public override void Enable(OptionSet options) options.Add("u=|username=", "The name of the user account", v => Username = v); options.Add("p=|password=", "The password for the user account", v => Password = v); } + + public override IEnumerable GetUsageErrors() + { + if (!Requirement.IsNonEmpty(Username)) yield return Requirement.NonEmptyDescription("username"); + if (!Requirement.IsNonEmpty(Password)) yield return Requirement.NonEmptyDescription("password"); + } } } diff --git a/src/Datalust.Piggy/Cli/Requirement.cs b/src/Datalust.Piggy/Cli/Requirement.cs new file mode 100644 index 0000000..4ce8dd1 --- /dev/null +++ b/src/Datalust.Piggy/Cli/Requirement.cs @@ -0,0 +1,15 @@ +namespace Datalust.Piggy.Cli +{ + static class Requirement + { + public static bool IsNonEmpty(string value) + { + return !string.IsNullOrWhiteSpace(value); + } + + public static string NonEmptyDescription(string optionName) + { + return $"a {optionName} is required"; + } + } +} From f7d9ec9a7f687137818f741635d62835c04a81dd Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 2 Aug 2017 20:46:39 +1000 Subject: [PATCH 2/4] Add piggy baseline to assume database and scripts are in sync --- .../Baseline/BaselineSession.cs | 45 +++++++++++++++++++ src/Datalust.Piggy/Cli/Command.cs | 2 +- src/Datalust.Piggy/Cli/CommandFeature.cs | 2 +- src/Datalust.Piggy/Cli/CommandLineHost.cs | 2 +- .../Cli/Commands/BaselineCommand.cs | 45 +++++++++++++++++++ .../Cli/Commands/HelpCommand.cs | 2 +- .../Cli/Commands/UpdateCommand.cs | 14 ++---- .../Cli/Features/ScriptRootFeature.cs | 22 +++++++++ src/Datalust.Piggy/Cli/Options.cs | 20 ++++----- .../Database/DatabaseConnector.cs | 7 +++ .../ChangeScriptFile.cs | 2 +- .../ChangeScriptFileEnumerator.cs | 5 ++- .../History/AppliedChangeScriptLog.cs | 10 +++-- src/Datalust.Piggy/Status/DatabaseStatus.cs | 25 +++++++++++ src/Datalust.Piggy/Update/UpdateSession.cs | 16 ++----- 15 files changed, 176 insertions(+), 43 deletions(-) create mode 100644 src/Datalust.Piggy/Baseline/BaselineSession.cs create mode 100644 src/Datalust.Piggy/Cli/Commands/BaselineCommand.cs create mode 100644 src/Datalust.Piggy/Cli/Features/ScriptRootFeature.cs rename src/Datalust.Piggy/{Update => Filesystem}/ChangeScriptFile.cs (89%) rename src/Datalust.Piggy/{Update => Filesystem}/ChangeScriptFileEnumerator.cs (85%) create mode 100644 src/Datalust.Piggy/Status/DatabaseStatus.cs diff --git a/src/Datalust.Piggy/Baseline/BaselineSession.cs b/src/Datalust.Piggy/Baseline/BaselineSession.cs new file mode 100644 index 0000000..1918b73 --- /dev/null +++ b/src/Datalust.Piggy/Baseline/BaselineSession.cs @@ -0,0 +1,45 @@ +using System.Text; +using Datalust.Piggy.Database; +using Datalust.Piggy.History; +using Datalust.Piggy.Status; +using Npgsql; +using Serilog; + +namespace Datalust.Piggy.Baseline +{ + class BaselineSession + { + public static void BaselineDatabase(string host, string database, string username, string password, string scriptRoot) + { + using (var connection = DatabaseConnector.Connect(host, database, username, password, false)) + { + var scripts = DatabaseStatus.GetPendingScripts(connection, scriptRoot); + + Log.Information("Found {Count} script files without change log records", scripts.Length); + + if (scripts.Length != 0) + { + var commandText = new StringBuilder(); + commandText.AppendLine("START TRANSACTION ISOLATION LEVEL REPEATABLE READ;"); + commandText.AppendLine(AppliedChangeScriptLog.ChangesTableCreateScript); + + foreach (var script in scripts) + { + Log.Information("Recording {FullPath} as {ScriptFile}", script.FullPath, script.RelativeName); + commandText.AppendLine(AppliedChangeScriptLog.CreateApplyLogScriptFor(script)); + } + + commandText.AppendLine("COMMIT TRANSACTION;"); + + Log.Information("Writing change log"); + using (var command = new NpgsqlCommand(commandText.ToString(), connection)) + { + command.ExecuteNonQuery(); + } + } + + Log.Information("Done"); + } + } + } +} diff --git a/src/Datalust.Piggy/Cli/Command.cs b/src/Datalust.Piggy/Cli/Command.cs index 1e41e01..370f70c 100644 --- a/src/Datalust.Piggy/Cli/Command.cs +++ b/src/Datalust.Piggy/Cli/Command.cs @@ -4,7 +4,7 @@ namespace Datalust.Piggy.Cli { - public abstract class Command + abstract class Command { readonly IList _features = new List(); diff --git a/src/Datalust.Piggy/Cli/CommandFeature.cs b/src/Datalust.Piggy/Cli/CommandFeature.cs index 4297e71..8a25ed3 100644 --- a/src/Datalust.Piggy/Cli/CommandFeature.cs +++ b/src/Datalust.Piggy/Cli/CommandFeature.cs @@ -3,7 +3,7 @@ namespace Datalust.Piggy.Cli { - public abstract class CommandFeature + abstract class CommandFeature { public abstract void Enable(OptionSet options); diff --git a/src/Datalust.Piggy/Cli/CommandLineHost.cs b/src/Datalust.Piggy/Cli/CommandLineHost.cs index b9ec002..316887b 100644 --- a/src/Datalust.Piggy/Cli/CommandLineHost.cs +++ b/src/Datalust.Piggy/Cli/CommandLineHost.cs @@ -6,7 +6,7 @@ namespace Datalust.Piggy.Cli { - public class CommandLineHost + class CommandLineHost { readonly List, CommandMetadata>> _availableCommands; diff --git a/src/Datalust.Piggy/Cli/Commands/BaselineCommand.cs b/src/Datalust.Piggy/Cli/Commands/BaselineCommand.cs new file mode 100644 index 0000000..05709c7 --- /dev/null +++ b/src/Datalust.Piggy/Cli/Commands/BaselineCommand.cs @@ -0,0 +1,45 @@ +using System; +using Datalust.Piggy.Baseline; +using Datalust.Piggy.Cli.Features; +using Datalust.Piggy.Update; +using Npgsql; +using Serilog; + +namespace Datalust.Piggy.Cli.Commands +{ + [Command("baseline", "Add scripts to the change log without running them")] + class BaselineCommand : Command + { + readonly UsernamePasswordFeature _usernamePasswordFeature; + readonly DatabaseFeature _databaseFeature; + readonly LoggingFeature _loggingFeature; + readonly ScriptRootFeature _scriptRootFeature; + + public BaselineCommand() + { + _databaseFeature = Enable(); + _usernamePasswordFeature = Enable(); + _scriptRootFeature = Enable(); + _loggingFeature = Enable(); + } + + protected override int Run() + { + _loggingFeature.Configure(); + + try + { + BaselineSession.BaselineDatabase( + _databaseFeature.Host, _databaseFeature.Database, _usernamePasswordFeature.Username, _usernamePasswordFeature.Password, + _scriptRootFeature.ScriptRoot); + + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Could not baseline the database"); + return -1; + } + } + } +} diff --git a/src/Datalust.Piggy/Cli/Commands/HelpCommand.cs b/src/Datalust.Piggy/Cli/Commands/HelpCommand.cs index 81e76f1..81cd59b 100644 --- a/src/Datalust.Piggy/Cli/Commands/HelpCommand.cs +++ b/src/Datalust.Piggy/Cli/Commands/HelpCommand.cs @@ -7,7 +7,7 @@ namespace Datalust.Piggy.Cli.Commands { [Command("help", "Show information about available commands")] - public class HelpCommand : Command + class HelpCommand : Command { readonly List, CommandMetadata>> _availableCommands; diff --git a/src/Datalust.Piggy/Cli/Commands/UpdateCommand.cs b/src/Datalust.Piggy/Cli/Commands/UpdateCommand.cs index a575920..c40324f 100644 --- a/src/Datalust.Piggy/Cli/Commands/UpdateCommand.cs +++ b/src/Datalust.Piggy/Cli/Commands/UpdateCommand.cs @@ -13,20 +13,15 @@ class UpdateCommand : Command readonly UsernamePasswordFeature _usernamePasswordFeature; readonly DatabaseFeature _databaseFeature; readonly LoggingFeature _loggingFeature; + readonly ScriptRootFeature _scriptRootFeature; - string _scriptRoot; bool _createIfMissing = true; public UpdateCommand() { _databaseFeature = Enable(); _usernamePasswordFeature = Enable(); - - Options.Add( - "s=|script-root=", - "The root directory to search for scripts", - v => _scriptRoot = v); - + _scriptRootFeature = Enable(); _defineVariablesFeature = Enable(); Options.Add("no-create", "If the database does not already exist, do not attempt to create it", v => _createIfMissing = false); @@ -38,14 +33,11 @@ protected override int Run() { _loggingFeature.Configure(); - if (!Require(_scriptRoot, "script root directory")) - return -1; - try { UpdateSession.ApplyChangeScripts( _databaseFeature.Host, _databaseFeature.Database, _usernamePasswordFeature.Username, _usernamePasswordFeature.Password, - _createIfMissing, _scriptRoot, _defineVariablesFeature.Variables); + _createIfMissing, _scriptRootFeature.ScriptRoot, _defineVariablesFeature.Variables); return 0; } diff --git a/src/Datalust.Piggy/Cli/Features/ScriptRootFeature.cs b/src/Datalust.Piggy/Cli/Features/ScriptRootFeature.cs new file mode 100644 index 0000000..0fc8b26 --- /dev/null +++ b/src/Datalust.Piggy/Cli/Features/ScriptRootFeature.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace Datalust.Piggy.Cli.Features +{ + class ScriptRootFeature : CommandFeature + { + public string ScriptRoot { get; set; } + + public override void Enable(OptionSet options) + { + options.Add( + "s=|script-root=", + "The root directory to search for scripts", + v => ScriptRoot = v); + } + + public override IEnumerable GetUsageErrors() + { + if (!Requirement.IsNonEmpty(ScriptRoot)) yield return Requirement.NonEmptyDescription("script root directory"); + } + } +} diff --git a/src/Datalust.Piggy/Cli/Options.cs b/src/Datalust.Piggy/Cli/Options.cs index 34a7c0a..8f43ae9 100644 --- a/src/Datalust.Piggy/Cli/Options.cs +++ b/src/Datalust.Piggy/Cli/Options.cs @@ -150,7 +150,7 @@ namespace NDesk.Options namespace Datalust.Piggy.Cli #endif { - public delegate U Converter(T t); + delegate U Converter(T t); static class StringCoda { @@ -234,7 +234,7 @@ private static int GetLineEnd (int start, int length, string description) } } - public class OptionValueCollection : IList, IList { + class OptionValueCollection : IList, IList { List values = new List (); OptionContext c; @@ -324,7 +324,7 @@ public override string ToString () } } - public class OptionContext { + class OptionContext { private Option option; private string name; private int index; @@ -361,13 +361,13 @@ public OptionValueCollection OptionValues { } } - public enum OptionValueType { + enum OptionValueType { None, Optional, Required, } - public abstract class Option { + abstract class Option { string prototype, description; string[] names; OptionValueType type; @@ -560,7 +560,7 @@ public override string ToString () } } - public abstract class ArgumentSource { + abstract class ArgumentSource { protected ArgumentSource () { @@ -624,7 +624,7 @@ static IEnumerable GetArguments (TextReader reader, bool close) } } - public class ResponseFileSource : ArgumentSource { + class ResponseFileSource : ArgumentSource { public override string[] GetNames () { @@ -646,7 +646,7 @@ public override bool GetArguments (string value, out IEnumerable replace } } - public class OptionException : Exception { + class OptionException : Exception { private string option; public OptionException () @@ -670,9 +670,9 @@ public string OptionName { } } - public delegate void OptionAction (TKey key, TValue value); + delegate void OptionAction (TKey key, TValue value); - public class OptionSet : KeyedCollection + class OptionSet : KeyedCollection { public OptionSet () : this (delegate (string f) {return f;}) diff --git a/src/Datalust.Piggy/Database/DatabaseConnector.cs b/src/Datalust.Piggy/Database/DatabaseConnector.cs index b363936..41b2e18 100644 --- a/src/Datalust.Piggy/Database/DatabaseConnector.cs +++ b/src/Datalust.Piggy/Database/DatabaseConnector.cs @@ -13,12 +13,17 @@ static DatabaseConnector() public static NpgsqlConnection Connect(string host, string database, string username, string password, bool createIfMissing) { + Log.Information("Connecting to database {Database} on {Host}", database, host); + var cstr = $"Host={host};Username={username};Password={password};Database={database}"; NpgsqlConnection conn = null; try { conn = new NpgsqlConnection(cstr); conn.Open(); + + Log.Information("Connected"); + return conn; } catch (PostgresException px) when (px.SqlState == "3D000") @@ -34,6 +39,8 @@ public static NpgsqlConnection Connect(string host, string database, string user static bool TryCreate(string host, string database, string username, string password) { + Log.Information("Database does not exist; attempting to create it"); + var postgresCstr = $"Host={host};Username={username};Password={password};Database=postgres"; using (var postgresConn = new NpgsqlConnection(postgresCstr)) { diff --git a/src/Datalust.Piggy/Update/ChangeScriptFile.cs b/src/Datalust.Piggy/Filesystem/ChangeScriptFile.cs similarity index 89% rename from src/Datalust.Piggy/Update/ChangeScriptFile.cs rename to src/Datalust.Piggy/Filesystem/ChangeScriptFile.cs index 9d04482..0cec6bc 100644 --- a/src/Datalust.Piggy/Update/ChangeScriptFile.cs +++ b/src/Datalust.Piggy/Filesystem/ChangeScriptFile.cs @@ -1,4 +1,4 @@ -namespace Datalust.Piggy.Update +namespace Datalust.Piggy.Filesystem { class ChangeScriptFile { diff --git a/src/Datalust.Piggy/Update/ChangeScriptFileEnumerator.cs b/src/Datalust.Piggy/Filesystem/ChangeScriptFileEnumerator.cs similarity index 85% rename from src/Datalust.Piggy/Update/ChangeScriptFileEnumerator.cs rename to src/Datalust.Piggy/Filesystem/ChangeScriptFileEnumerator.cs index 82af06c..cb7c623 100644 --- a/src/Datalust.Piggy/Update/ChangeScriptFileEnumerator.cs +++ b/src/Datalust.Piggy/Filesystem/ChangeScriptFileEnumerator.cs @@ -1,13 +1,16 @@ using System; using System.IO; using System.Linq; +using Serilog; -namespace Datalust.Piggy.Update +namespace Datalust.Piggy.Filesystem { static class ChangeScriptFileEnumerator { public static ChangeScriptFile[] EnumerateInOrder(string scriptRoot) { + Log.Information("Searching for *.sql change script files in {ScriptRoot}", scriptRoot); + if (!Directory.Exists(scriptRoot)) throw new ArgumentException("The script root directory does not exist"); diff --git a/src/Datalust.Piggy/History/AppliedChangeScriptLog.cs b/src/Datalust.Piggy/History/AppliedChangeScriptLog.cs index bea3a1d..5a8a424 100644 --- a/src/Datalust.Piggy/History/AppliedChangeScriptLog.cs +++ b/src/Datalust.Piggy/History/AppliedChangeScriptLog.cs @@ -1,5 +1,5 @@ using Dapper; -using Datalust.Piggy.Update; +using Datalust.Piggy.Filesystem; using Npgsql; using Serilog; @@ -24,13 +24,17 @@ appliedorder serial not null public static AppliedChangeScript[] GetAppliedChangeScripts(NpgsqlConnection connection) { + Log.Information("Loading applied change scripts from the database"); + try { - return connection.Query($"SELECT scriptfile, appliedat, appliedby, appliedorder FROM {ChangesTableName} ORDER BY appliedorder ASC;").AsList().ToArray(); + var changes = connection.Query($"SELECT scriptfile, appliedat, appliedby, appliedorder FROM {ChangesTableName} ORDER BY appliedorder ASC;").AsList().ToArray(); + Log.Information("Database history contains {AppliedCount} applied changes", changes.Length); + return changes; } catch (PostgresException px) when (px.SqlState == "42P01") { - Log.Debug(px, "Change log table does not exist"); + Log.Information("Database history is empty (change log table does not exist)"); return new AppliedChangeScript[0]; } } diff --git a/src/Datalust.Piggy/Status/DatabaseStatus.cs b/src/Datalust.Piggy/Status/DatabaseStatus.cs new file mode 100644 index 0000000..06d917b --- /dev/null +++ b/src/Datalust.Piggy/Status/DatabaseStatus.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Datalust.Piggy.Filesystem; +using Datalust.Piggy.History; +using Npgsql; + +namespace Datalust.Piggy.Status +{ + static class DatabaseStatus + { + public static ChangeScriptFile[] GetPendingScripts(NpgsqlConnection connection, string scriptRoot) + { + if (connection == null) throw new ArgumentNullException(nameof(connection)); + if (scriptRoot == null) throw new ArgumentNullException(nameof(scriptRoot)); + + var changes = AppliedChangeScriptLog.GetAppliedChangeScripts(connection); + + var applied = new HashSet(changes.Select(m => m.ScriptFile)); + return ChangeScriptFileEnumerator.EnumerateInOrder(scriptRoot) + .Where(s => !applied.Contains(s.RelativeName)) + .ToArray(); + } + } +} diff --git a/src/Datalust.Piggy/Update/UpdateSession.cs b/src/Datalust.Piggy/Update/UpdateSession.cs index d497c5f..51a662e 100644 --- a/src/Datalust.Piggy/Update/UpdateSession.cs +++ b/src/Datalust.Piggy/Update/UpdateSession.cs @@ -1,9 +1,10 @@ using System.Collections.Generic; using System.IO; -using System.Linq; using System.Text; using Datalust.Piggy.Database; +using Datalust.Piggy.Filesystem; using Datalust.Piggy.History; +using Datalust.Piggy.Status; using Datalust.Piggy.Syntax; using Npgsql; using Serilog; @@ -15,20 +16,9 @@ static class UpdateSession public static void ApplyChangeScripts(string host, string database, string username, string password, bool createIfMissing, string scriptRoot, IReadOnlyDictionary variables) { - Log.Information("Connecting to database {Database} on {Host}", database, host); using (var connection = DatabaseConnector.Connect(host, database, username, password, createIfMissing)) { - Log.Information("Connected"); - - Log.Information("Loading applied change scripts from the database"); - var changes = AppliedChangeScriptLog.GetAppliedChangeScripts(connection); - Log.Information("Database history contains {AppliedCount} applied changes", changes.Length); - - Log.Information("Searching for *.sql change script files in {ScriptRoot}", scriptRoot); - var applied = new HashSet(changes.Select(m => m.ScriptFile)); - var scripts = ChangeScriptFileEnumerator.EnumerateInOrder(scriptRoot) - .Where(s => !applied.Contains(s.RelativeName)) - .ToArray(); + var scripts = DatabaseStatus.GetPendingScripts(connection, scriptRoot); Log.Information("Found {Count} new script files to apply", scripts.Length); From 3a5831755b95520c40511b578880bfced9df3274 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 2 Aug 2017 21:08:46 +1000 Subject: [PATCH 3/4] Add piggy pending command --- src/Datalust.Piggy/Cli/CommandLineHost.cs | 2 +- .../Cli/Commands/HelpCommand.cs | 2 +- src/Datalust.Piggy/Cli/Commands/LogCommand.cs | 6 +-- .../Cli/Commands/PendingCommand.cs | 49 +++++++++++++++++++ .../Cli/Features/LoggingFeature.cs | 7 +-- 5 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 src/Datalust.Piggy/Cli/Commands/PendingCommand.cs diff --git a/src/Datalust.Piggy/Cli/CommandLineHost.cs b/src/Datalust.Piggy/Cli/CommandLineHost.cs index 316887b..1c0870a 100644 --- a/src/Datalust.Piggy/Cli/CommandLineHost.cs +++ b/src/Datalust.Piggy/Cli/CommandLineHost.cs @@ -31,7 +31,7 @@ public int Run(string[] args) } Console.WriteLine($"Usage: {name} []"); - Console.WriteLine($"Type '{name} help' for available commands"); + Console.WriteLine($"Type `{name} help` for available commands"); return -1; } } diff --git a/src/Datalust.Piggy/Cli/Commands/HelpCommand.cs b/src/Datalust.Piggy/Cli/Commands/HelpCommand.cs index 81cd59b..eadf175 100644 --- a/src/Datalust.Piggy/Cli/Commands/HelpCommand.cs +++ b/src/Datalust.Piggy/Cli/Commands/HelpCommand.cs @@ -54,7 +54,7 @@ protected override int Run(string[] unrecognised) } Console.WriteLine(); - Console.WriteLine($"Type '{name} help ' for detailed help."); + Console.WriteLine($"Type `{name} help ` for detailed help"); return 0; } diff --git a/src/Datalust.Piggy/Cli/Commands/LogCommand.cs b/src/Datalust.Piggy/Cli/Commands/LogCommand.cs index 420ea50..a0dabb5 100644 --- a/src/Datalust.Piggy/Cli/Commands/LogCommand.cs +++ b/src/Datalust.Piggy/Cli/Commands/LogCommand.cs @@ -24,10 +24,6 @@ protected override int Run() { _loggingFeature.Configure(); - if (!(Require(_databaseFeature.Host, "host") && Require(_databaseFeature.Database, "database") && - Require("username", _usernamePasswordFeature.Username) && Require("password", _usernamePasswordFeature.Password))) - return -1; - try { using (var connection = DatabaseConnector.Connect(_databaseFeature.Host, _databaseFeature.Database, @@ -43,7 +39,7 @@ protected override int Run() } catch (Exception ex) { - Log.Fatal(ex, "Could not apply change scripts"); + Log.Fatal(ex, "Could not list change scripts"); return -1; } } diff --git a/src/Datalust.Piggy/Cli/Commands/PendingCommand.cs b/src/Datalust.Piggy/Cli/Commands/PendingCommand.cs new file mode 100644 index 0000000..b7f3685 --- /dev/null +++ b/src/Datalust.Piggy/Cli/Commands/PendingCommand.cs @@ -0,0 +1,49 @@ +using System; +using Datalust.Piggy.Cli.Features; +using Datalust.Piggy.Database; +using Datalust.Piggy.Status; +using Serilog; + +namespace Datalust.Piggy.Cli.Commands +{ + [Command("pending", "Determine which scripts will be run in an update")] + class PendingCommand : Command + { + readonly UsernamePasswordFeature _usernamePasswordFeature; + readonly DatabaseFeature _databaseFeature; + readonly LoggingFeature _loggingFeature; + readonly ScriptRootFeature _scriptRootFeature; + + public PendingCommand() + { + _databaseFeature = Enable(); + _usernamePasswordFeature = Enable(); + _scriptRootFeature = Enable(); + _loggingFeature = Enable(); + } + + protected override int Run() + { + _loggingFeature.Configure(); + + try + { + using (var connection = DatabaseConnector.Connect(_databaseFeature.Host, _databaseFeature.Database, + _usernamePasswordFeature.Username, _usernamePasswordFeature.Password, false)) + { + foreach (var pending in DatabaseStatus.GetPendingScripts(connection, _scriptRootFeature.ScriptRoot)) + { + Console.WriteLine($"{pending.RelativeName}"); + } + } + + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Could not determine pending change scripts"); + return -1; + } + } + } +} diff --git a/src/Datalust.Piggy/Cli/Features/LoggingFeature.cs b/src/Datalust.Piggy/Cli/Features/LoggingFeature.cs index 254ac66..e33bdb0 100644 --- a/src/Datalust.Piggy/Cli/Features/LoggingFeature.cs +++ b/src/Datalust.Piggy/Cli/Features/LoggingFeature.cs @@ -7,13 +7,14 @@ namespace Datalust.Piggy.Cli.Features class LoggingFeature : CommandFeature { string _serverUrl, _apiKey; - LogEventLevel _level = LogEventLevel.Information; + LogEventLevel _level = LogEventLevel.Information, _consoleLevel = LevelAlias.Minimum; public override void Enable(OptionSet options) { + options.Add("quiet", "Limit diagnostic terminal output to errors only", v => _consoleLevel = LogEventLevel.Error); + options.Add("debug", "Write additional diagnostic log output", v => _level = LogEventLevel.Debug); options.Add("log-seq=", "Log output to a Seq server at the specified URL", v => _serverUrl = v); options.Add("log-seq-apikey=", "If logging to Seq, an optional API key", v => _apiKey = v); - options.Add("log-debug", "Write additional diagnostic log output", v => _level = LogEventLevel.Debug); } public void Configure() @@ -22,7 +23,7 @@ public void Configure() .MinimumLevel.Is(_level) .Enrich.WithProperty("Application", "Piggy") .Enrich.WithProperty("Invocation", Guid.NewGuid()) - .WriteTo.Console(); + .WriteTo.Console(standardErrorFromLevel: LogEventLevel.Error, restrictedToMinimumLevel: _consoleLevel); if (!string.IsNullOrWhiteSpace(_serverUrl)) loggerConfiguration From c6bcb0fd706493afd6c830603e669ced1bfc0f8d Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Thu, 3 Aug 2017 08:27:22 +1000 Subject: [PATCH 4/4] Update README [Skip CI] --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 001b347..fbb25f4 100644 --- a/README.md +++ b/README.md @@ -81,3 +81,23 @@ piggy up ... -v schema=myapp -v admin=myuser ### Change log The change log is stored in the target database in `piggy.changes`. The `piggy log` command is used to view applied change scripts. + +### Help + +Run `piggy help` to see all available commands, and `piggy help ` for detailed command help. + +``` +> piggy help +Usage: piggy [] + +Available commands are: + baseline Add scripts to the change log without running them + help Show information about available commands + log List change scripts that have been applied to a database + pending Determine which scripts will be run in an update + up Bring a database up to date + --version Print the current executable version + +Type `piggy help ` for detailed help +``` +