diff --git a/.editorconfig b/.editorconfig index 2e42bbbad8..cb9df1a499 100644 --- a/.editorconfig +++ b/.editorconfig @@ -282,7 +282,7 @@ dotnet_diagnostic.SA1637.severity = none dotnet_diagnostic.SX1101.severity = warning - +resharper_inconsistent_naming_highlighting = none ## Visual Studio generated .editorconfig file with C++ settings. diff --git a/Tests/Realm.Tests/Sync/SyncTestHelpers.cs b/Tests/Realm.Tests/Sync/SyncTestHelpers.cs index 27e473e21d..3e9dcdfec1 100644 --- a/Tests/Realm.Tests/Sync/SyncTestHelpers.cs +++ b/Tests/Realm.Tests/Sync/SyncTestHelpers.cs @@ -62,7 +62,7 @@ static SyncTestHelpers() BaseUri = BaasUri ?? new Uri("http://localhost:12345"), MetadataPersistenceMode = MetadataPersistenceMode.NotEncrypted, #pragma warning disable CA1837 // Use Environment.ProcessId instead of Process.GetCurrentProcess().Id - BaseFilePath = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), $"rt-sync-{System.Diagnostics.Process.GetCurrentProcess().Id}-{_appCounter++}")).FullName + BaseFilePath = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), $"rt-sync-{Process.GetCurrentProcess().Id}-{_appCounter++}")).FullName #pragma warning restore CA1837 // Use Environment.ProcessId instead of Process.GetCurrentProcess().Id }; @@ -126,33 +126,41 @@ public static async Task ExtractBaasSettingsAsync(string[] args) return remainingArgs; } + private class LogArgs + { + public string? RealmLogLevel { get; set; } + + public string? RealmLogFile { get; set; } + } + public static (string[] RemainingArgs, IDisposable? Logger) SetLoggerFromArgs(string[] args) { - var (extracted, remaining) = BaasClient.ExtractArguments(args, "realmloglevel", "realmlogfile"); + var (extracted, remaining) = BaasClient.ExtractArguments(args); - if (extracted.TryGetValue("realmloglevel", out var logLevelStr) && Enum.TryParse(logLevelStr, out var logLevel)) + if (!string.IsNullOrEmpty(extracted.RealmLogLevel)) { + var logLevel = (LogLevel)Enum.Parse(typeof(LogLevel), extracted.RealmLogLevel!); TestHelpers.Output.WriteLine($"Setting log level to {logLevel}"); Logger.LogLevel = logLevel; } Logger.AsyncFileLogger? logger = null; - if (extracted.TryGetValue("realmlogfile", out var logFile)) + if (!string.IsNullOrEmpty(extracted.RealmLogFile)) { if (!Process.GetCurrentProcess().ProcessName.ToLower().Contains("testhost")) { - TestHelpers.Output.WriteLine($"Setting sync logger to file: {logFile}"); + TestHelpers.Output.WriteLine($"Setting sync logger to file: {extracted.RealmLogFile}"); // We're running in a test runner, so we need to use the sync logger - Logger.Default = Logger.File(logFile); + Logger.Default = Logger.File(extracted.RealmLogFile!); } else { - TestHelpers.Output.WriteLine($"Setting async logger to file: {logFile}"); + TestHelpers.Output.WriteLine($"Setting async logger to file: {extracted.RealmLogFile}"); // We're running standalone (likely on CI), so we use the async logger - Logger.Default = logger = new Logger.AsyncFileLogger(logFile); + Logger.Default = logger = new Logger.AsyncFileLogger(extracted.RealmLogFile!); } } diff --git a/Tools/DeployApps/BaasClient.cs b/Tools/DeployApps/BaasClient.cs index 343143b2f1..5559b819b3 100644 --- a/Tools/DeployApps/BaasClient.cs +++ b/Tools/DeployApps/BaasClient.cs @@ -18,21 +18,21 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Reflection; using System.Security.Cryptography; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Serializers; -#if !NETCOREAPP2_1_OR_GREATER -using System.Diagnostics.CodeAnalysis; -#endif namespace Baas { @@ -229,6 +229,26 @@ public static async Task Atlas(Uri baseUri, string differentiator, T return result; } + private class BaasArgs + { + public string? BaasaasApiKey { get; set; } + + public string? BaasUrl { get; set; } + + public string? BaasCluster { get; set; } + + public string? BaasApiKey { get; set; } + + public string? BaasPrivateApiKey { get; set; } + + public string? BaasProjectId { get; set; } + + public string BaasDifferentiator { get; set; } = "local"; + + [MemberNotNullWhen(false, nameof(BaasCluster), nameof(BaasApiKey), nameof(BaasPrivateApiKey), nameof(BaasProjectId))] + public bool UseDocker => BaasCluster == null; + } + public static async Task<(BaasClient? Client, Uri? BaseUrl, string[] RemainingArgs)> CreateClientFromArgs(string[] args, TextWriter output) { if (args == null) @@ -236,32 +256,30 @@ public static async Task Atlas(Uri baseUri, string differentiator, T throw new ArgumentNullException(nameof(args)); } - var (extracted, remaining) = ExtractArguments(args, "baasaas-api-key", "baas-url", "baas-cluster", - "baas-api-key", "baas-private-api-key", "baas-projectid", "baas-differentiator"); + var (extracted, remaining) = ExtractArguments(args); - var differentiator = extracted.GetValueOrDefault("baas-differentiator", "local"); + var differentiator = extracted.BaasDifferentiator; BaasClient client; Uri baseUri; - if (extracted.TryGetValue("baasaas-api-key", out var baasaasApiKey) && !string.IsNullOrEmpty(baasaasApiKey)) + if (!string.IsNullOrEmpty(extracted.BaasaasApiKey)) { - baseUri = await GetOrDeployContainer(baasaasApiKey, differentiator, output); + baseUri = await GetOrDeployContainer(extracted.BaasaasApiKey!, differentiator, output); client = await Docker(baseUri, differentiator, output); } else { - if (!extracted.TryGetValue("baasurl", out var baseUrl) || string.IsNullOrEmpty(baseUrl)) + if (string.IsNullOrEmpty(extracted.BaasUrl)) { return (null, null, remaining); } - baseUri = new Uri(baseUrl); - var baasCluster = extracted.GetValueOrDefault("baascluster"); + baseUri = new Uri(extracted.BaasUrl!); - client = string.IsNullOrEmpty(baasCluster) + client = extracted.UseDocker ? await Docker(baseUri, differentiator, output) - : await Atlas(baseUri, differentiator, output, baasCluster!, extracted["baasapikey"], extracted["baasprivateapikey"], extracted["baasprojectid"]); + : await Atlas(baseUri, differentiator, output, extracted.BaasCluster, extracted.BaasApiKey, extracted.BaasPrivateApiKey, extracted.BaasProjectId); } return (client, baseUri, remaining); @@ -274,16 +292,16 @@ public static async Task TerminateBaasFromArgs(string[] args, TextWriter output) throw new ArgumentNullException(nameof(args)); } - var (extracted, _) = ExtractArguments(args, "baasaas-api-key", "baas-differentiator"); + var (extracted, _) = ExtractArguments(args); - var differentiator = extracted.GetValueOrDefault("baas-differentiator", "local"); + var differentiator = extracted.BaasDifferentiator; - if (!extracted.TryGetValue("baasaas-api-key", out var baaSaasApiKey) || string.IsNullOrEmpty(baaSaasApiKey)) + if (string.IsNullOrEmpty(extracted.BaasaasApiKey)) { - throw new InvalidOperationException("Need a BaaSaas API key to terminate containers"); + throw new InvalidOperationException("Need a Baasaas API key to terminate containers"); } - await StopContainer(baaSaasApiKey, differentiator, output); + await StopContainer(extracted.BaasaasApiKey!, differentiator, output); } public string GetSyncDatabaseName(string appType = AppConfigType.Default) => $"Sync_{Differentiator}_{appType}"; @@ -805,36 +823,41 @@ public static HttpContent GetJsonContent(object obj) return content; } - public static (Dictionary Extracted, string[] RemainingArgs) ExtractArguments(string[] args, params string[] toExtract) + // Extract arguments and populate a type T with the extracted values. Only string readwrite properties + // will be considered. + public static (T Extracted, string[] RemainingArgs) ExtractArguments(string[] args) + where T : new() { if (args == null) { throw new ArgumentNullException(nameof(args)); } - var extracted = new Dictionary(); - var remainingArgs = new List(); - for (var i = 0; i < args.Length; i++) - { - if (!toExtract.Any(name => ExtractArg(i, name))) - { - remainingArgs.Add(args[i]); - } - } + var argsToExtract = typeof(T).GetProperties() + .Where(p => p.PropertyType == typeof(string) && p is { CanRead: true, CanWrite: true }) + .Select(p => (ArgName: GetArgName(p.Name), PropertyInfo: p)) + .ToArray(); - return (extracted, remainingArgs.ToArray()); + var extracted = new T(); + return (extracted, args.Where(a => !argsToExtract.Any(kvp => ExtractArg(a, kvp))).ToArray()); - bool ExtractArg(int index, string name) + bool ExtractArg(string arg, (string ArgName, PropertyInfo Info) prop) { - var arg = args[index]; - if (arg.StartsWith($"--{name}=")) + if (arg.StartsWith($"--{prop.ArgName}=")) { - extracted[name] = arg.Replace($"--{name}=", string.Empty); + prop.Info.SetValue(extracted, arg.Replace($"--{prop.ArgName}=", string.Empty)); return true; } return false; } + + static string GetArgName(string propertyName) + { + return Regex.Replace(propertyName, "(?