Always load database config at start

Ensures that the database settings used by ef tools are those in config.
This commit is contained in:
Noi 2022-07-22 23:41:49 -07:00
parent c3ecf2a877
commit 584a55cd60
3 changed files with 62 additions and 35 deletions

View file

@ -1,22 +1,22 @@
using Microsoft.EntityFrameworkCore;
using Npgsql;
namespace RegexBot.Data;
/// <summary>
/// Represents a database connection using the settings defined in the bot's global configuration.
/// </summary>
public class BotDatabaseContext : DbContext {
private static string? _npgsqlConnectionString;
internal static string PostgresConnectionString {
#if DEBUG
get {
if (_npgsqlConnectionString != null) return _npgsqlConnectionString;
Console.WriteLine($"{nameof(RegexBot)} - {nameof(BotDatabaseContext)} note: Using hardcoded connection string!");
return _npgsqlConnectionString ?? "Host=localhost;Username=regexbot;Password=rb";
}
#else
get => _npgsqlConnectionString!;
#endif
set => _npgsqlConnectionString ??= value;
private static readonly string _connectionString;
static BotDatabaseContext() {
// Get our own config loaded just for the SQL stuff
var conf = new InstanceConfig();
_connectionString = new NpgsqlConnectionStringBuilder() {
Host = conf.SqlHost ?? "localhost", // default to localhost
Database = conf.SqlDatabase,
Username = conf.SqlUsername,
Password = conf.SqlPassword
}.ToString();
}
/// <summary>
@ -37,7 +37,7 @@ public class BotDatabaseContext : DbContext {
/// <inheritdoc />
protected sealed override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseNpgsql(PostgresConnectionString)
.UseNpgsql(_connectionString)
.UseSnakeCaseNamingConvention();
/// <inheritdoc />

View file

@ -1,5 +1,6 @@
using Newtonsoft.Json;
using RegexBot.Data;
using CommandLine;
using Newtonsoft.Json;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
namespace RegexBot;
@ -18,20 +19,20 @@ class InstanceConfig {
/// List of assemblies to load, by file. Paths are always relative to the bot directory.
/// </summary>
internal IReadOnlyList<string> Assemblies { get; }
/// <summary>
/// Webhook URL for bot log reporting.
/// </summary>
internal string InstanceLogTarget { get; }
// TODO add fields for services to be configurable: DMRelay
public string? SqlHost { get; }
public string? SqlDatabase { get; }
public string SqlUsername { get; }
public string SqlPassword { get; }
/// <summary>
/// Sets up instance configuration object from file and command line parameters.
/// </summary>
internal InstanceConfig() {
var path = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location)
+ "." + Path.DirectorySeparatorChar + "instance.json";
var args = CommandLineParameters.Parse(Environment.GetCommandLineArgs());
var path = args?.ConfigFile ?? Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location)
+ Path.DirectorySeparatorChar + "." + Path.DirectorySeparatorChar + "instance.json";
JObject conf;
try {
@ -45,19 +46,8 @@ class InstanceConfig {
throw new Exception(pfx + ex.Message, ex);
}
// Input validation - throw exception on errors. Exception messages printed as-is.
BotToken = conf[nameof(BotToken)]?.Value<string>()!;
if (string.IsNullOrEmpty(BotToken))
throw new Exception($"'{nameof(BotToken)}' is not properly specified in configuration.");
var pginput = conf[nameof(BotDatabaseContext.PostgresConnectionString)]?.Value<string>()!;
if (string.IsNullOrEmpty(pginput))
throw new Exception($"'{nameof(BotDatabaseContext.PostgresConnectionString)}' is not properly specified in configuration.");
BotDatabaseContext.PostgresConnectionString = pginput;
InstanceLogTarget = conf[nameof(InstanceLogTarget)]?.Value<string>()!;
if (string.IsNullOrEmpty(InstanceLogTarget))
throw new Exception($"'{nameof(InstanceLogTarget)}' is not properly specified in configuration.");
BotToken = ReadConfKey<string>(conf, nameof(BotToken), true);
InstanceLogTarget = ReadConfKey<string>(conf, nameof(InstanceLogTarget), true);
try {
Assemblies = Common.Utilities.LoadStringOrStringArray(conf[nameof(Assemblies)]).AsReadOnly();
@ -66,5 +56,41 @@ class InstanceConfig {
} catch (ArgumentException) {
throw new Exception($"'{nameof(Assemblies)}' is not properly specified in configuration.");
}
SqlHost = ReadConfKey<string>(conf, nameof(SqlHost), false);
SqlDatabase = ReadConfKey<string?>(conf, nameof(SqlDatabase), false);
SqlUsername = ReadConfKey<string>(conf, nameof(SqlUsername), true);
SqlPassword = ReadConfKey<string>(conf, nameof(SqlPassword), true);
}
private static T? ReadConfKey<T>(JObject jc, string key, [DoesNotReturnIf(true)] bool failOnEmpty) {
if (jc.ContainsKey(key)) return jc[key]!.Value<T>();
if (failOnEmpty) throw new Exception($"'{key}' must be specified in the instance configuration.");
return default;
}
/// <summary>
/// Command line options
/// </summary>
class CommandLineParameters {
[Option('c', "config", Default = null,
HelpText = "Custom path to instance configuration. Defaults to instance.json in bot directory.")]
public string ConfigFile { get; set; } = null!;
/// <summary>
/// Command line arguments parsed here. Depending on inputs, the program can exit here.
/// </summary>
public static CommandLineParameters? Parse(string[] args) {
CommandLineParameters? result = null;
new Parser(settings => {
settings.IgnoreUnknownArguments = true;
settings.AutoHelp = false;
settings.AutoVersion = false;
}).ParseArguments<CommandLineParameters>(args)
.WithParsed(p => result = p)
.WithNotParsed(e => { /* ignore */ });
return result;
}
}
}

View file

@ -20,6 +20,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Discord.Net" Version="3.7.2" />
<PackageReference Include="EFCore.NamingConventions" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.7" />