src/common/EnvironmentVariables.cs (184 lines of code) (raw):

// -------------------------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // -------------------------------------------------------------------------------------------- using System; using System.Globalization; using Microsoft.BridgeToKubernetes.Common.Exceptions; using Microsoft.BridgeToKubernetes.Common.IO; using Microsoft.BridgeToKubernetes.Common.Logging; namespace Microsoft.BridgeToKubernetes.Common { internal class EnvironmentVariables : IEnvironmentVariables { // For some reason the this variable is empty if referenced before it is declared. So this needs to be at the top. protected static readonly string BRIDGE = Constants.Product.NameAbbreviation.ToUpperInvariant(); public static class Names { public static readonly string BridgeEnvironment = $"{BRIDGE}_ENVIRONMENT"; public static readonly string ConsoleVerbosity = "CONSOLE_VERBOSITY"; public static readonly string LogFileVerbosity = "LOG_FILE_VERBOSITY"; public static readonly string TelemetryVerbosity = "TELEMETRY_VERBOSITY"; public static readonly string Home = "HOME"; public static readonly string UserProfile = "USERPROFILE"; public static readonly string DevHostImageName = $"{BRIDGE}_DEVHOSTIMAGENAME"; public static readonly string RestorationJobImageName = $"{BRIDGE}_RESTORATIONJOBIMAGENAME"; public static readonly string RoutingManagerImageName = $"{BRIDGE}_ROUTINGMANAGERIMAGENAME"; public static readonly string SourceUserAgent = $"{BRIDGE}_SOURCE_USER_AGENT"; public static readonly string CorrelationId = $"{BRIDGE}_CORRELATION_ID"; public static readonly string CollectTelemetry = $"{BRIDGE}_COLLECT_TELEMETRY"; public static readonly string EnableLogFile = $"{BRIDGE}_ENABLE_LOG_FILE"; public static readonly string Culture = $"{BRIDGE}_CULTURE"; public static readonly string KubernetesInClusterConfigOverride = "KUBERNETES_IN_CLUSTER_CONFIG_OVERWRITE"; public static readonly string KubeConfig = "KUBECONFIG"; public static readonly string KubernetesServiceHost = "KUBERNETES_SERVICE_HOST"; public static readonly string KubernetesServicePort = "KUBERNETES_SERVICE_PORT"; public static readonly string DotNetRoot = "DOTNET_ROOT"; public static readonly string IsCodespaces = $"{BRIDGE}_IS_CODESPACES"; public static readonly string KubectlProxy = "KUBECTL_PROXY"; } private readonly Lazy<ReleaseEnvironment> _releaseEnvironment = new Lazy<ReleaseEnvironment>(() => GetReleaseEnvironment(Get(Names.BridgeEnvironment))); private readonly Lazy<LoggingVerbosity> _consoleLoggingVerbosity; private readonly Lazy<LoggingVerbosity> _logFileVerbosity; private readonly Lazy<LoggingVerbosity> _telemetryVerbosity; private readonly Lazy<string> _home = new Lazy<string>(() => GetRequired(Names.Home)); private readonly Lazy<string> _userName; private readonly Lazy<string> _userProfile = new Lazy<string>(() => GetRequired(Names.UserProfile)); private readonly Lazy<string> _devHostImageName = new Lazy<string>(() => Get(Names.DevHostImageName)); private readonly Lazy<string> _devHostRestorationJobImageName = new Lazy<string>(() => Get(Names.RestorationJobImageName)); private readonly Lazy<string> _routingManagerImageName = new Lazy<string>(() => Get(Names.RoutingManagerImageName)); private readonly Lazy<string> _sourceUserAgent = new Lazy<string>(() => Get(Names.SourceUserAgent)); private readonly Lazy<string> _correlationId = new Lazy<string>(() => Get(Names.CorrelationId)); private readonly Lazy<bool> _collectTelemetry = new Lazy<bool>(() => GetBool(Names.CollectTelemetry, defaultValue: false)); private readonly Lazy<bool> _enableLogFile = new Lazy<bool>(() => GetBool(Names.EnableLogFile, defaultValue: true)); private readonly Lazy<CultureInfo> _culture = new Lazy<CultureInfo>(() => GetCultureInfo(Names.Culture)); private readonly Lazy<string> _kubernetesInClusterConfigOverwrite = new Lazy<string>(() => Get(Names.KubernetesInClusterConfigOverride)); private readonly Lazy<string> _kubeConfig = new Lazy<string>(() => Get(Names.KubeConfig)); private readonly Lazy<string> _kubernetesServiceHost = new Lazy<string>(() => Get(Names.KubernetesServiceHost)); private readonly Lazy<string> _kubernetesServicePort = new Lazy<string>(() => Get(Names.KubernetesServicePort)); private readonly Lazy<string> _dotNetRoot = new Lazy<string>(() => Get(Names.DotNetRoot)); private readonly Lazy<bool> _isCodespaces = new Lazy<bool>(() => GetBool(Names.IsCodespaces, defaultValue: false)); private readonly Lazy<string> _kubectlProxy = new Lazy<string>(() => Get(Names.KubectlProxy)); public EnvironmentVariables(IPlatform platform) { _userName = new Lazy<string>(() => platform.IsWindows ? GetRequired("USERNAME") : GetRequired("USER")); _consoleLoggingVerbosity = new Lazy<LoggingVerbosity>(() => GetLoggingVerbosity(Names.ConsoleVerbosity, defaultProductionVerbosity: LoggingVerbosity.Normal)); _logFileVerbosity = new Lazy<LoggingVerbosity>(() => GetLoggingVerbosity(Names.LogFileVerbosity, defaultProductionVerbosity: LoggingVerbosity.Verbose)); _telemetryVerbosity = new Lazy<LoggingVerbosity>(() => GetLoggingVerbosity(Get(Names.TelemetryVerbosity), defaultProductionVerbosity: LoggingVerbosity.Verbose)); } #region IEnvironmentVariables public virtual ReleaseEnvironment ReleaseEnvironment => _releaseEnvironment.Value; public virtual string Home => _home.Value; public virtual string UserName => _userName.Value; public virtual string UserProfile => _userProfile.Value; public virtual string DevHostImageName => _devHostImageName.Value; public virtual string DevHostRestorationJobImageName => _devHostRestorationJobImageName.Value; public virtual string RoutingManagerImageName => _routingManagerImageName.Value; public virtual LoggingVerbosity ConsoleLoggingVerbosity => _consoleLoggingVerbosity.Value; public virtual LoggingVerbosity LogFileVerbosity => _logFileVerbosity.Value; public virtual LoggingVerbosity TelemetryVerbosity => _telemetryVerbosity.Value; public virtual string SourceUserAgent => _sourceUserAgent.Value; public virtual string CorrelationId => _correlationId.Value; public virtual bool CollectTelemetry => _collectTelemetry.Value; public virtual bool EnableLogFile => _enableLogFile.Value; public virtual CultureInfo Culture => _culture.Value; public virtual string KubernetesInClusterConfigOverwrite => _kubernetesInClusterConfigOverwrite.Value; public virtual string KubeConfig => _kubeConfig.Value; public virtual string KubernetesServiceHost => _kubernetesServiceHost.Value; public virtual string KubernetesServicePort => _kubernetesServicePort.Value; public string DotNetRoot => _dotNetRoot.Value; public bool IsCodespaces => _isCodespaces.Value; public string KubectlProxy => _kubectlProxy.Value; #endregion IEnvironmentVariables /// <summary> /// Gets the value of a system environment variable /// </summary> /// <param name="name">Environment variable name</param> /// <returns>The string value of the environment variable, or null if the variable is not defined</returns> protected static string Get(string name) { var value = string.IsNullOrWhiteSpace(name) ? null : Environment.GetEnvironmentVariable(name); return string.IsNullOrWhiteSpace(value) ? null : value.Trim(); } /// <summary> /// Checks if a given environment variable used as a flag has a value indicating it is enabled, like "1" or "true". /// </summary> /// <param name="name">The name of the environment variable.</param> /// <param name="defaultValue">The default value to use if the environment value is not set or set to an invalid (not bool-like) value</param> /// <returns>true if the variable is enabled, false otherwise.</returns> protected static bool GetBool(string name, bool defaultValue) { var value = Get(name); // If the environment variable is not set or is empty, return the default value if (string.IsNullOrWhiteSpace(value)) { return defaultValue; } // If the Environment variable exactly matches an expected bool-like value, return the corresponding bool value if (value.Equals("1") || StringComparer.OrdinalIgnoreCase.Equals(value, "true") || StringComparer.OrdinalIgnoreCase.Equals(value, "yes") || StringComparer.OrdinalIgnoreCase.Equals(value, "on")) { return true; } if (value.Equals("0") || StringComparer.OrdinalIgnoreCase.Equals(value, "false") || StringComparer.OrdinalIgnoreCase.Equals(value, "no") || StringComparer.OrdinalIgnoreCase.Equals(value, "off")) { return false; } // The environment variable is set, but not to an expected bool-like value, so return the default value return defaultValue; } /// <summary> /// Retrieves an environment variable value and converts it to an int /// </summary> /// <param name="name">The name of the environment variable.</param> /// <returns>The value of the environment variable, converted to an int, or -1 if the environment variable is not defined or its value cannot be parsed</returns> /// <throws>An <see cref="ArgumentNullException"/> if the environment variable is not defined</throws> /// <throws>A <see cref="FormatException"/> if the environment variable value cannot be parsed as an int</throws> /// <throws>An <see cref="OverflowException"/> if the parsed number would overflow int</throws> protected static int GetRequiredInt(string name) => int.Parse(GetRequired(name)); /// <summary> /// Retrieves an environment variable value and converts it to an int /// </summary> /// <param name="name">The name of the environment variable.</param> /// <returns>The value of the environment variable, converted to an int, or null if the environment variable is not defined or its value cannot be parsed</returns> protected static int? GetInt(string name) { if (int.TryParse(Get(name), out int value)) { return value; } return null; } /// <summary> /// Retrieves an environment variable value and converts it to a double /// </summary> /// <param name="name">The name of the environment variable</param> /// <returns>The value of the environment variable, converted to a double, or null if the environment variable is not defined or its value cannot be parsed</returns> protected static double? GetDouble(string name) => double.TryParse(Get(name), out double value) ? (double?)value : null; /// <summary> /// Retrieves an environment variable value and converts it to a double /// </summary> /// <param name="name">The name of the environment variable.</param> /// <returns>The value of the environment variable, converted to a double</returns> /// <throws>An <see cref="ArgumentNullException"/> if the environment variable is not defined</throws> /// <throws>A <see cref="FormatException"/> if the environment variable value cannot be parsed as a double</throws> /// <throws>An <see cref="OverflowException"/> if the parsed number would overflow double</throws> protected static double GetRequiredDouble(string name) => double.Parse(GetRequired(name)); /// <summary> /// Retrieves an environment variable value and converts it to a Guid /// </summary> /// <param name="name">The name of the environment variable.</param> /// <returns>The value of the environment variable, converted to a Guid</returns> /// <throws>An <see cref="ArgumentNullException"/> if the environment variable is not defined</throws> /// <throws>A <see cref="FormatException"/> if the environment variable value cannot be parsed as a Guid</throws> protected static Guid GetRequiredGuid(string name) => Guid.Parse(GetRequired(name)); /// <summary> /// Get the value of an environment variable that must be defined /// </summary> /// <param name="name">Environment variable name</param> /// <throws>A <see cref="ConfigurationException"/> if the variable is not set</throws> /// <returns>The environment variable's value</returns> protected static string GetRequired(string name) { var value = Get(name); if (value == null) { throw new ConfigurationException(name, "Environment variable is not defined!"); } return value; } /// <summary> /// Gets the current <see cref="Common.ReleaseEnvironment"/> from the environment variable /// </summary> private static ReleaseEnvironment GetReleaseEnvironment(string name) { switch (name?.ToLowerInvariant()) { case "local": return ReleaseEnvironment.Local; case "development": case "dev": return ReleaseEnvironment.Development; case "staging": case "stage": return ReleaseEnvironment.Staging; case "test": return ReleaseEnvironment.Test; default: return ReleaseEnvironment.Production; } } /// <summary> /// Determines the <see cref="LoggingVerbosity"/> by reading the environment variable named <paramref name="name"/>, /// and falling back on smart defaults based on the current <see cref="ReleaseEnvironment"/> /// </summary> /// <returns> /// The <see cref="LoggingVerbosity"/> specified by the <paramref name="name"/> environment variable. /// If the <paramref name="name"/> environment variable is not set: in production returns <paramref name="defaultProductionVerbosity"/>, /// in other environments returns <see cref="LoggingVerbosity.Verbose"/> /// </returns> private LoggingVerbosity GetLoggingVerbosity(string name, LoggingVerbosity defaultProductionVerbosity) { var value = Get(name); switch (value?.ToLowerInvariant()) { case "quiet": return LoggingVerbosity.Quiet; case "normal": return LoggingVerbosity.Normal; case "verbose": return LoggingVerbosity.Verbose; default: return this.ReleaseEnvironment.IsProduction() ? defaultProductionVerbosity : LoggingVerbosity.Verbose; } } private static CultureInfo GetCultureInfo(string name) { var culture = Get(name); if (!string.IsNullOrWhiteSpace(culture)) { try { return CultureInfo.CreateSpecificCulture(culture); } catch { } } return CultureInfo.CurrentUICulture; } } }