From 69f45352c90fae016c33c4e6c1294105898269cc Mon Sep 17 00:00:00 2001 From: Aniruddh Munde Date: Thu, 7 Sep 2023 13:51:59 -0700 Subject: [PATCH] [Cherry pick] Fix for Config File Path not found (#1693) - Cherry Picks #1681 --------- Co-authored-by: abhishekkumams <102276754+abhishekkumams@users.noreply.github.com> --- src/Cli/ConfigGenerator.cs | 2 +- src/Cli/ConfigMerger.cs | 2 +- src/Config/FileSystemRuntimeConfigLoader.cs | 82 +++++++++++++------ .../AuthenticationConfigValidatorUnitTests.cs | 10 +-- .../Unittests/ConfigValidationUnitTests.cs | 66 +++++++++++++++ src/Service/Startup.cs | 2 +- 6 files changed, 132 insertions(+), 32 deletions(-) diff --git a/src/Cli/ConfigGenerator.cs b/src/Cli/ConfigGenerator.cs index 45c4a7404d..dc6eb4bf24 100644 --- a/src/Cli/ConfigGenerator.cs +++ b/src/Cli/ConfigGenerator.cs @@ -965,7 +965,7 @@ public static bool TryStartEngineWithOptions(StartOptions options, FileSystemRun return false; } - loader.UpdateConfigFileName(runtimeConfigFile); + loader.UpdateConfigFilePath(runtimeConfigFile); // Validates that config file has data and follows the correct json schema // Replaces all the environment variables while deserializing when starting DAB. diff --git a/src/Cli/ConfigMerger.cs b/src/Cli/ConfigMerger.cs index a765db5d6d..55a9ae6ad2 100644 --- a/src/Cli/ConfigMerger.cs +++ b/src/Cli/ConfigMerger.cs @@ -24,7 +24,7 @@ public static bool TryMergeConfigsIfAvailable(IFileSystem fileSystem, FileSystem string baseConfigFile = FileSystemRuntimeConfigLoader.DEFAULT_CONFIG_FILE_NAME; string environmentBasedConfigFile = loader.GetFileName(environmentValue, considerOverrides: false); - if (loader.DoesFileExistInCurrentDirectory(baseConfigFile) && !string.IsNullOrEmpty(environmentBasedConfigFile)) + if (loader.DoesFileExistInDirectory(baseConfigFile) && !string.IsNullOrEmpty(environmentBasedConfigFile)) { try { diff --git a/src/Config/FileSystemRuntimeConfigLoader.cs b/src/Config/FileSystemRuntimeConfigLoader.cs index 8a0c0afbe8..1ce068d148 100644 --- a/src/Config/FileSystemRuntimeConfigLoader.cs +++ b/src/Config/FileSystemRuntimeConfigLoader.cs @@ -26,8 +26,8 @@ namespace Azure.DataApiBuilder.Config; public class FileSystemRuntimeConfigLoader : RuntimeConfigLoader { // This stores either the default config name e.g. dab-config.json - // or user provided config file. - private string _baseConfigFileName; + // or user provided config file which could be a relative file path, absolute file path or simply the file name assumed to be in current directory. + private string _baseConfigFilePath; private readonly IFileSystem _fileSystem; @@ -45,19 +45,20 @@ public class FileSystemRuntimeConfigLoader : RuntimeConfigLoader public const string DEFAULT_CONFIG_FILE_NAME = $"{CONFIGFILE_NAME}{CONFIG_EXTENSION}"; /// - /// Stores the config file name actually loaded by the engine. + /// Stores the config file actually loaded by the engine. /// It could be the base config file (e.g. dab-config.json), any of its derivatives with /// environment specific suffixes (e.g. dab-config.Development.json) or the user provided /// config file name. + /// It could also be the config file provided by the user. /// - public string ConfigFileName { get; internal set; } + public string ConfigFilePath { get; internal set; } - public FileSystemRuntimeConfigLoader(IFileSystem fileSystem, string baseConfigFileName = DEFAULT_CONFIG_FILE_NAME, string? connectionString = null) + public FileSystemRuntimeConfigLoader(IFileSystem fileSystem, string baseConfigFilePath = DEFAULT_CONFIG_FILE_NAME, string? connectionString = null) : base(connectionString) { _fileSystem = fileSystem; - _baseConfigFileName = baseConfigFileName; - ConfigFileName = GetFileNameForEnvironment(Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"), false); + _baseConfigFilePath = baseConfigFilePath; + ConfigFilePath = GetFinalConfigFilePath(); } /// @@ -75,6 +76,7 @@ public bool TryLoadConfig( { if (_fileSystem.File.Exists(path)) { + Console.WriteLine($"Loading config file from {path}."); string json = _fileSystem.File.ReadAllText(path); return TryParseConfig(json, out config, connectionString: _connectionString, replaceEnvVar: replaceEnvVar); } @@ -98,7 +100,7 @@ public bool TryLoadConfig( /// True if the config was loaded, otherwise false. public override bool TryLoadKnownConfig([NotNullWhen(true)] out RuntimeConfig? config, bool replaceEnvVar = false) { - return TryLoadConfig(ConfigFileName, out config, replaceEnvVar); + return TryLoadConfig(ConfigFilePath, out config, replaceEnvVar); } /// @@ -143,6 +145,29 @@ public string GetFileNameForEnvironment(string? aspnetEnvironment, bool consider return configFileNameWithExtension; } + /// + /// This method returns the final config file name that will be used by the runtime engine. + /// + private string GetFinalConfigFilePath() + { + if (!string.Equals(_baseConfigFilePath, DEFAULT_CONFIG_FILE_NAME)) + { + // user provided config file is honoured. + return _baseConfigFilePath; + } + + // ConfigFile not explicitly provided by user, so we need to get the config file name based on environment. + string configFilePath = GetFileNameForEnvironment(Environment.GetEnvironmentVariable(ASP_NET_CORE_ENVIRONMENT_VAR_NAME), false); + + // If file for environment is not found, then the baseConfigFile is used as the final configFile for runtime engine. + if (string.IsNullOrWhiteSpace(configFilePath)) + { + return _baseConfigFilePath; + } + + return configFilePath; + } + /// /// Generates the config file name and a corresponding overridden file name, /// With precedence given to overridden file name, returns that name @@ -154,21 +179,24 @@ public string GetFileNameForEnvironment(string? aspnetEnvironment, bool consider /// public string GetFileName(string? environmentValue, bool considerOverrides) { - string fileNameWithoutExtension = _fileSystem.Path.GetFileNameWithoutExtension(_baseConfigFileName); - string fileExtension = _fileSystem.Path.GetExtension(_baseConfigFileName); - string configFileName = + // If the baseConfigFilePath contains directory info, we need to ensure that it is not lost. for example: baseConfigFilePath = "config/dab-config.json" + // in this case, we need to get the directory name and the file name without extension and then combine them back. Else, we will lose the path + // and the file will be searched in the current directory. + string filePathWithoutExtension = _fileSystem.Path.Combine(_fileSystem.Path.GetDirectoryName(_baseConfigFilePath) ?? string.Empty, _fileSystem.Path.GetFileNameWithoutExtension(_baseConfigFilePath)); + string fileExtension = _fileSystem.Path.GetExtension(_baseConfigFilePath); + string configFilePath = !string.IsNullOrEmpty(environmentValue) - ? $"{fileNameWithoutExtension}.{environmentValue}" - : $"{fileNameWithoutExtension}"; - string configFileNameWithExtension = $"{configFileName}{fileExtension}"; - string overriddenConfigFileNameWithExtension = GetOverriddenName(configFileName); + ? $"{filePathWithoutExtension}.{environmentValue}" + : $"{filePathWithoutExtension}"; + string configFileNameWithExtension = $"{configFilePath}{fileExtension}"; + string overriddenConfigFileNameWithExtension = GetOverriddenName(configFilePath); - if (considerOverrides && DoesFileExistInCurrentDirectory(overriddenConfigFileNameWithExtension)) + if (considerOverrides && DoesFileExistInDirectory(overriddenConfigFileNameWithExtension)) { return overriddenConfigFileNameWithExtension; } - if (DoesFileExistInCurrentDirectory(configFileNameWithExtension)) + if (DoesFileExistInDirectory(configFileNameWithExtension)) { return configFileNameWithExtension; } @@ -176,9 +204,9 @@ public string GetFileName(string? environmentValue, bool considerOverrides) return string.Empty; } - private static string GetOverriddenName(string fileName) + private static string GetOverriddenName(string filePath) { - return $"{fileName}.overrides{CONFIG_EXTENSION}"; + return $"{filePath}.overrides{CONFIG_EXTENSION}"; } /// @@ -190,10 +218,16 @@ public static string GetEnvironmentFileName(string fileName, string environmentV return $"{fileName}.{environmentValue}{CONFIG_EXTENSION}"; } - public bool DoesFileExistInCurrentDirectory(string fileName) + /// + /// Checks if the file exists in the directory. + /// Works for both relative and absolute paths. + /// + /// + /// True if file is found, else false. + public bool DoesFileExistInDirectory(string filePath) { string currentDir = _fileSystem.Directory.GetCurrentDirectory(); - return _fileSystem.File.Exists(_fileSystem.Path.Combine(currentDir, fileName)); + return _fileSystem.File.Exists(_fileSystem.Path.Combine(currentDir, filePath)); } /// @@ -256,9 +290,9 @@ public static string GetMergedFileNameForEnvironment(string fileName, string env /// to be updated. This is commonly done when the CLI is starting up. /// /// - public void UpdateConfigFileName(string fileName) + public void UpdateConfigFilePath(string filePath) { - _baseConfigFileName = fileName; - ConfigFileName = fileName; + _baseConfigFilePath = filePath; + ConfigFilePath = filePath; } } diff --git a/src/Service.Tests/Configuration/AuthenticationConfigValidatorUnitTests.cs b/src/Service.Tests/Configuration/AuthenticationConfigValidatorUnitTests.cs index a44f95e5be..a068e6300a 100644 --- a/src/Service.Tests/Configuration/AuthenticationConfigValidatorUnitTests.cs +++ b/src/Service.Tests/Configuration/AuthenticationConfigValidatorUnitTests.cs @@ -47,7 +47,7 @@ public void ValidateEasyAuthConfig() // Since we added the config file to the filesystem above after the config loader was initialized // in TestInitialize, we need to update the ConfigfileName, otherwise it will be an empty string. - _runtimeConfigLoader.UpdateConfigFileName(FileSystemRuntimeConfigLoader.DEFAULT_CONFIG_FILE_NAME); + _runtimeConfigLoader.UpdateConfigFilePath(FileSystemRuntimeConfigLoader.DEFAULT_CONFIG_FILE_NAME); try { @@ -75,7 +75,7 @@ public void ValidateJwtConfigParamsSet() new MockFileData(config.ToJson()) ); - _runtimeConfigLoader.UpdateConfigFileName(FileSystemRuntimeConfigLoader.DEFAULT_CONFIG_FILE_NAME); + _runtimeConfigLoader.UpdateConfigFilePath(FileSystemRuntimeConfigLoader.DEFAULT_CONFIG_FILE_NAME); try { @@ -96,7 +96,7 @@ public void ValidateAuthNSectionNotNecessary() new MockFileData(config.ToJson()) ); - _runtimeConfigLoader.UpdateConfigFileName(FileSystemRuntimeConfigLoader.DEFAULT_CONFIG_FILE_NAME); + _runtimeConfigLoader.UpdateConfigFilePath(FileSystemRuntimeConfigLoader.DEFAULT_CONFIG_FILE_NAME); try { @@ -125,7 +125,7 @@ public void ValidateFailureWithIncompleteJwtConfig() new MockFileData(config.ToJson()) ); - _runtimeConfigLoader.UpdateConfigFileName(FileSystemRuntimeConfigLoader.DEFAULT_CONFIG_FILE_NAME); + _runtimeConfigLoader.UpdateConfigFilePath(FileSystemRuntimeConfigLoader.DEFAULT_CONFIG_FILE_NAME); Assert.ThrowsException(() => { @@ -160,7 +160,7 @@ public void ValidateFailureWithUnneededEasyAuthConfig() new MockFileData(config.ToJson()) ); - _runtimeConfigLoader.UpdateConfigFileName(FileSystemRuntimeConfigLoader.DEFAULT_CONFIG_FILE_NAME); + _runtimeConfigLoader.UpdateConfigFilePath(FileSystemRuntimeConfigLoader.DEFAULT_CONFIG_FILE_NAME); Assert.ThrowsException(() => { diff --git a/src/Service.Tests/Unittests/ConfigValidationUnitTests.cs b/src/Service.Tests/Unittests/ConfigValidationUnitTests.cs index 4aa6127f09..5bce00fc1c 100644 --- a/src/Service.Tests/Unittests/ConfigValidationUnitTests.cs +++ b/src/Service.Tests/Unittests/ConfigValidationUnitTests.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Data; +using System.IO; using System.IO.Abstractions.TestingHelpers; using System.Linq; using System.Net; @@ -2016,6 +2017,71 @@ public void ValidateRuntimeBaseRouteSettings( } } + /// + /// This test checks that the final config used by runtime engine doesn't lose the directory information + /// if provided by the user. + /// It also validates that if config file is provided by the user, it will be used directly irrespective of + /// environment variable being set or not. + /// When user doesn't provide a config file, we check if environment variable is set and if it is, we use + /// the config file specified by the environment variable, else we use the default config file. + /// + /// + /// + /// + /// + [DataTestMethod] + [DataRow("my-config.json", "", false, null, "my-config.json", DisplayName = "Config file in the current directory provided by user and environment variable is not set")] + [DataRow("test-configs/my-config.json", "", false, null, "test-configs/my-config.json", DisplayName = "Config file in different directory provided by user and environment variable is not set")] + [DataRow("my-config.json", "Test", false, "my-config.Test.json", "my-config.json", DisplayName = "Config file in the current directory provided by user and environment variable is set")] + [DataRow("test-configs/my-config.json", "Test", false, "test-configs/my-config.Test.json", "test-configs/my-config.json", DisplayName = "Config file in different directory provided by user and environment variable is set")] + [DataRow("my-config.json", "Test", false, "dab-config.Test.json", "my-config.json", DisplayName = "Config file in the current directory provided by user and environment variable is set and environment file is present")] + [DataRow("test-configs/my-config.json", "Test", false, "test-configs/dab-config.Test.json", "test-configs/my-config.json", DisplayName = "Config file in different directory provided by user and environment variable is set and environment file is present")] + [DataRow("my-config.json", "", true, null, "my-config.json", DisplayName = "Config file in the current directory provided by user and environment variable is not set and absolute path is provided")] + [DataRow("test-configs/my-config.json", "", true, null, "test-configs/my-config.json", DisplayName = "Config file in different directory provided by user and environment variable is not set and absolute path is provided")] + [DataRow("my-config.json", "Test", true, "my-config.Test.json", "my-config.json", DisplayName = "Config file in the current directory provided by user and environment variable is set and absolute path is provided")] + [DataRow("test-configs/my-config.json", "Test", true, "test-configs/my-config.Test.json", "test-configs/my-config.json", DisplayName = "Config file in different directory provided by user and environment variable is set and absolute path is provided")] + [DataRow("my-config.json", "Test", true, "dab-config.Test.json", "my-config.json", DisplayName = "Config file in the current directory provided by user and environment variable is set and environment file is present and absolute path is provided")] + [DataRow("test-configs/my-config.json", "Test", true, "test-configs/dab-config.Test.json", "test-configs/my-config.json", DisplayName = "Config file in the different directory provided by user and environment variable is set and environment file is present and absolute path is provided")] + [DataRow(null, "", false, null, "dab-config.json", DisplayName = "Config file not provided by user and environment variable is not set")] + [DataRow(null, "Test", false, "dab-config.Test.json", "dab-config.json", DisplayName = "Config file not provided by user and environment variable is set and environment file is present")] + [DataRow(null, "Test", false, null, "dab-config.json", DisplayName = "Config file not provided by user and environment variable is set and environment file is not present")] + public void TestCorrectConfigFileIsSelectedForRuntimeEngine( + string userProvidedConfigFilePath, + string environmentValue, + bool useAbsolutePath, + string environmentFile, + string finalConfigFilePath) + { + MockFileSystem fileSystem = new(); + if (!string.IsNullOrWhiteSpace(Path.GetDirectoryName(userProvidedConfigFilePath))) + { + fileSystem.AddDirectory("test-configs"); + } + + if (useAbsolutePath) + { + userProvidedConfigFilePath = fileSystem.Path.GetFullPath(userProvidedConfigFilePath); + finalConfigFilePath = fileSystem.Path.GetFullPath(finalConfigFilePath); + } + + if (environmentFile is not null) + { + fileSystem.AddEmptyFile(environmentFile); + } + + FileSystemRuntimeConfigLoader runtimeConfigLoader; + if (userProvidedConfigFilePath is not null) + { + runtimeConfigLoader = new(fileSystem, userProvidedConfigFilePath); + } + else + { + runtimeConfigLoader = new(fileSystem); + } + + Assert.AreEqual(finalConfigFilePath, runtimeConfigLoader.ConfigFilePath); + } + private static RuntimeConfigValidator InitializeRuntimeConfigValidator() { MockFileSystem fileSystem = new(); diff --git a/src/Service/Startup.cs b/src/Service/Startup.cs index 3df435a3b3..95414a4e8e 100644 --- a/src/Service/Startup.cs +++ b/src/Service/Startup.cs @@ -303,7 +303,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, RuntimeC _logger.LogError("Exiting the runtime engine..."); } - throw new ApplicationException($"Could not initialize the engine with the runtime config file: {fileSystemRuntimeConfigLoader.ConfigFileName}"); + throw new ApplicationException($"Could not initialize the engine with the runtime config file: {fileSystemRuntimeConfigLoader.ConfigFilePath}"); } } else