From 654cf06615981b190737101d3a1df39e57e20341 Mon Sep 17 00:00:00 2001 From: Brennan Date: Fri, 27 Mar 2020 10:56:49 -0700 Subject: [PATCH] Backport ANCM environment variable additions (#20006) --- .../AspNetCore/ShimOptions.cpp | 5 ++ .../CommonLib/ConfigurationSection.h | 2 + .../InProcessOptions.cpp | 6 ++ .../environmentvariablehash.h | 1 + .../environmentvariablehelpers.h | 20 ++++++- .../requesthandler_config.cpp | 56 ++++++++++++++----- .../Inprocess/EnvironmentVariableTests.cs | 17 ++++++ .../Inprocess/StartupTests.cs | 33 +++++++++++ .../RequiresIISAttribute.cs | 5 ++ 9 files changed, 131 insertions(+), 14 deletions(-) diff --git a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp index 208d4a1eaa..353b0be788 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp @@ -54,6 +54,11 @@ ShimOptions::ShimOptions(const ConfigurationSource &configurationSource) : .value_or(environmentVariables[CS_ASPNETCORE_ENVIRONMENT]); const auto dotnetEnvironment = Environment::GetEnvironmentVariableValue(CS_DOTNET_ENVIRONMENT) .value_or(environmentVariables[CS_DOTNET_ENVIRONMENT]); + // We prefer the environment variables for LAUNCHER_PATH and LAUNCHER_ARGS + m_strProcessPath = Environment::GetEnvironmentVariableValue(CS_ANCM_LAUNCHER_PATH) + .value_or(m_strProcessPath); + m_strArguments = Environment::GetEnvironmentVariableValue(CS_ANCM_LAUNCHER_ARGS) + .value_or(m_strArguments); auto detailedErrorsEnabled = equals_ignore_case(L"1", detailedErrors) || equals_ignore_case(L"true", detailedErrors); auto aspnetCoreEnvironmentEnabled = equals_ignore_case(L"Development", aspnetCoreEnvironment); diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h index ae02dd3faa..8c9cece3e3 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h @@ -33,6 +33,8 @@ #define CS_ASPNETCORE_DETAILEDERRORS L"ASPNETCORE_DETAILEDERRORS" #define CS_ASPNETCORE_ENVIRONMENT L"ASPNETCORE_ENVIRONMENT" #define CS_DOTNET_ENVIRONMENT L"DOTNET_ENVIRONMENT" +#define CS_ANCM_LAUNCHER_PATH L"ANCM_LAUNCHER_PATH" +#define CS_ANCM_LAUNCHER_ARGS L"ANCM_LAUNCHER_ARGS" class ConfigurationSection: NonCopyable { diff --git a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp index 01ec10f6f6..c2ff5e0a7d 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp @@ -4,6 +4,7 @@ #include "InProcessOptions.h" #include "InvalidOperationException.h" #include "EventLog.h" +#include "Environment.h" HRESULT InProcessOptions::Create( IHttpServer& pServer, @@ -51,6 +52,11 @@ InProcessOptions::InProcessOptions(const ConfigurationSource &configurationSourc auto const aspNetCoreSection = configurationSource.GetRequiredSection(CS_ASPNETCORE_SECTION); m_strArguments = aspNetCoreSection->GetString(CS_ASPNETCORE_PROCESS_ARGUMENTS).value_or(CS_ASPNETCORE_PROCESS_ARGUMENTS_DEFAULT); m_strProcessPath = aspNetCoreSection->GetRequiredString(CS_ASPNETCORE_PROCESS_EXE_PATH); + // We prefer the environment variables for LAUNCHER_PATH and LAUNCHER_ARGS + m_strProcessPath = Environment::GetEnvironmentVariableValue(CS_ANCM_LAUNCHER_PATH) + .value_or(m_strProcessPath); + m_strArguments = Environment::GetEnvironmentVariableValue(CS_ANCM_LAUNCHER_ARGS) + .value_or(m_strArguments); m_fStdoutLogEnabled = aspNetCoreSection->GetRequiredBool(CS_ASPNETCORE_STDOUT_LOG_ENABLED); m_struStdoutLogFile = aspNetCoreSection->GetRequiredString(CS_ASPNETCORE_STDOUT_LOG_FILE); m_fDisableStartUpErrorPage = aspNetCoreSection->GetRequiredBool(CS_ASPNETCORE_DISABLE_START_UP_ERROR_PAGE); diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehash.h b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehash.h index 82541f1bdf..248ca1aa93 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehash.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehash.h @@ -13,6 +13,7 @@ #define ASPNETCORE_IIS_AUTH_BASIC L"basic;" #define ASPNETCORE_IIS_AUTH_ANONYMOUS L"anonymous;" #define ASPNETCORE_IIS_AUTH_NONE L"none" +#define ANCM_PREFER_ENVIRONMENT_VARIABLES_ENV_STR L"ANCM_PREFER_ENVIRONMENT_VARIABLES" // // The key used for hash-table lookups, consists of the port on which the http process is created. diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehelpers.h b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehelpers.h index 2842dc0245..3a6fef9335 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehelpers.h +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehelpers.h @@ -78,9 +78,27 @@ public: environmentVariables.insert_or_assign(HOSTING_STARTUP_ASSEMBLIES_ENV_STR, hostingStartupValues); } + auto preferEnvironmentVariablesSetting = Environment::GetEnvironmentVariableValue(ANCM_PREFER_ENVIRONMENT_VARIABLES_ENV_STR).value_or(L"false"); + auto preferEnvironmentVariables = equals_ignore_case(L"1", preferEnvironmentVariablesSetting) || equals_ignore_case(L"true", preferEnvironmentVariablesSetting); + for (auto& environmentVariable : environmentVariables) { - environmentVariable.second = Environment::ExpandEnvironmentVariables(environmentVariable.second); + if (preferEnvironmentVariables) + { + auto env = Environment::GetEnvironmentVariableValue(environmentVariable.first); + if (env.has_value()) + { + environmentVariable.second = env.value(); + } + else + { + environmentVariable.second = Environment::ExpandEnvironmentVariables(environmentVariable.second); + } + } + else + { + environmentVariable.second = Environment::ExpandEnvironmentVariables(environmentVariable.second); + } } return environmentVariables; diff --git a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.cpp b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.cpp index d3a705e9a2..2b68345623 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/RequestHandlerLib/requesthandler_config.cpp @@ -7,6 +7,7 @@ #include "environmentvariablehash.h" #include "exceptions.h" #include "config_utility.h" +#include "Environment.h" REQUESTHANDLER_CONFIG::~REQUESTHANDLER_CONFIG() { @@ -101,6 +102,8 @@ REQUESTHANDLER_CONFIG::Populate( BSTR bstrBasicAuthSection = NULL; BSTR bstrAnonymousAuthSection = NULL; BSTR bstrAspNetCoreSection = NULL; + std::optional launcherPathEnv; + std::optional launcherArgsEnv; pAdminManager = pHttpServer->GetAdminManager(); try @@ -248,12 +251,47 @@ REQUESTHANDLER_CONFIG::Populate( goto Finished; } - hr = GetElementStringProperty(pAspNetCoreElement, - CS_ASPNETCORE_PROCESS_EXE_PATH, - &m_struProcessPath); - if (FAILED(hr)) + // We prefer the environment variables for LAUNCHER_PATH and LAUNCHER_ARGS + try { - goto Finished; + launcherPathEnv = Environment::GetEnvironmentVariableValue(CS_ANCM_LAUNCHER_PATH); + launcherArgsEnv = Environment::GetEnvironmentVariableValue(CS_ANCM_LAUNCHER_ARGS); + } + catch(...) + { + FINISHED_IF_FAILED(E_FAIL); + } + + if (launcherPathEnv.has_value()) + { + hr = m_struProcessPath.Copy(launcherPathEnv.value().c_str()); + FINISHED_IF_FAILED(hr); + } + else + { + hr = GetElementStringProperty(pAspNetCoreElement, + CS_ASPNETCORE_PROCESS_EXE_PATH, + &m_struProcessPath); + if (FAILED(hr)) + { + goto Finished; + } + } + + if (launcherArgsEnv.has_value()) + { + hr = m_struArguments.Copy(launcherArgsEnv.value().c_str()); + FINISHED_IF_FAILED(hr); + } + else + { + hr = GetElementStringProperty(pAspNetCoreElement, + CS_ASPNETCORE_PROCESS_ARGUMENTS, + &m_struArguments); + if (FAILED(hr)) + { + goto Finished; + } } hr = GetElementStringProperty(pAspNetCoreElement, @@ -281,14 +319,6 @@ REQUESTHANDLER_CONFIG::Populate( goto Finished; } - hr = GetElementStringProperty(pAspNetCoreElement, - CS_ASPNETCORE_PROCESS_ARGUMENTS, - &m_struArguments); - if (FAILED(hr)) - { - goto Finished; - } - hr = GetElementDWORDProperty(pAspNetCoreElement, CS_ASPNETCORE_RAPID_FAILS_PER_MINUTE, &m_dwRapidFailsPerMinute); diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/EnvironmentVariableTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/EnvironmentVariableTests.cs index 7f8d003eb7..2a8140aec5 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/EnvironmentVariableTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/EnvironmentVariableTests.cs @@ -126,5 +126,22 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess deploymentParameters.WebConfigBasedEnvironmentVariables["OtherVariable"] = "%TestVariable%;Hello"; Assert.Equal("World;Hello", await GetStringAsync(deploymentParameters, "/GetEnvironmentVariable?name=OtherVariable")); } + + [ConditionalTheory] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] + [RequiresNewHandler] + [RequiresNewShim] + [InlineData(HostingModel.InProcess)] + [InlineData(HostingModel.OutOfProcess)] + public async Task PreferEnvironmentVariablesOverWebConfigWhenConfigured(HostingModel hostingModel) + { + var deploymentParameters = Fixture.GetBaseDeploymentParameters(hostingModel); + + var environment = "Development"; + deploymentParameters.EnvironmentVariables["ANCM_PREFER_ENVIRONMENT_VARIABLES"] = "true"; + deploymentParameters.EnvironmentVariables["ASPNETCORE_ENVIRONMENT"] = environment; + deploymentParameters.WebConfigBasedEnvironmentVariables.Add("ASPNETCORE_ENVIRONMENT", "Debug"); + Assert.Equal(environment, await GetStringAsync(deploymentParameters, "/GetEnvironmentVariable?name=ASPNETCORE_ENVIRONMENT")); + } } } diff --git a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs index 1c070242e0..2d152fccca 100644 --- a/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs +++ b/src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs @@ -875,6 +875,39 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.InProcess Assert.True(result.IsSuccessStatusCode); } + [ConditionalTheory] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] + [RequiresNewShim] + [RequiresNewHandler] + [InlineData(HostingModel.InProcess)] + [InlineData(HostingModel.OutOfProcess)] + public async Task EnvironmentVariableForLauncherPathIsPreferred(HostingModel hostingModel) + { + var deploymentParameters = Fixture.GetBaseDeploymentParameters(hostingModel); + + deploymentParameters.EnvironmentVariables["ANCM_LAUNCHER_PATH"] = _dotnetLocation; + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", "nope")); + + await StartAsync(deploymentParameters); + } + + [ConditionalTheory] + [RequiresIIS(IISCapability.PoolEnvironmentVariables)] + [RequiresNewShim] + [RequiresNewHandler] + [InlineData(HostingModel.InProcess)] + [InlineData(HostingModel.OutOfProcess)] + public async Task EnvironmentVariableForLauncherArgsIsPreferred(HostingModel hostingModel) + { + var deploymentParameters = Fixture.GetBaseDeploymentParameters(hostingModel); + using var publishedApp = await deploymentParameters.ApplicationPublisher.Publish(deploymentParameters, LoggerFactory.CreateLogger("test")); + + deploymentParameters.EnvironmentVariables["ANCM_LAUNCHER_ARGS"] = Path.ChangeExtension(Path.Combine(publishedApp.Path, deploymentParameters.ApplicationPublisher.ApplicationPath), ".dll"); + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("arguments", "nope")); + + await StartAsync(deploymentParameters); + } + private static void VerifyDotnetRuntimeEventLog(IISDeploymentResult deploymentResult) { var entries = GetEventLogsFromDotnetRuntime(deploymentResult); diff --git a/src/Servers/IIS/IIS/test/IIS.Shared.FunctionalTests/RequiresIISAttribute.cs b/src/Servers/IIS/IIS/test/IIS.Shared.FunctionalTests/RequiresIISAttribute.cs index fad488d484..d6fc23a803 100644 --- a/src/Servers/IIS/IIS/test/IIS.Shared.FunctionalTests/RequiresIISAttribute.cs +++ b/src/Servers/IIS/IIS/test/IIS.Shared.FunctionalTests/RequiresIISAttribute.cs @@ -61,6 +61,11 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests var ancmConfigPath = Path.Combine(Environment.SystemDirectory, "inetsrv", "config", "schema", "aspnetcore_schema.xml"); + if (!File.Exists(ancmConfigPath)) + { + ancmConfigPath = Path.Combine(Environment.SystemDirectory, "inetsrv", "config", "schema", "aspnetcore_schema_v2.xml"); + } + if (!File.Exists(ancmConfigPath) && !SkipInVSTSAttribute.RunningInVSTS) { _skipReasonStatic = "IIS Schema is not installed.";