Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions DevProxy.Tests/EnvOptionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using DevProxy.Commands;
using DevProxy.Proxy;
using Microsoft.Extensions.Configuration;

using Xunit;

namespace DevProxy.Tests;

public class EnvOptionTests
{
[Fact]
public void Env_ParsesCommandLineValues()
{
var options = new DevProxyConfigOptions();
options.ParseOptions(["--env", "FOO=bar", "-e", "URL=https://x?a=b"]);

var env = options.Env;

Assert.NotNull(env);
Assert.Equal("bar", env["FOO"]);
Assert.Equal("https://x?a=b", env["URL"]);
}

[Fact]
public void Env_NotSpecified_ReturnsNull()
{
var options = new DevProxyConfigOptions();
options.ParseOptions(["--port", "0"]);

Assert.Null(options.Env);
}

[Fact]
public void SetEnvironmentVariables_SetsConfigAndCommandLineValues_CommandLineWins()
{
var prefix = $"DEVPROXY_TEST_{Guid.NewGuid():N}_";
var fromConfig = prefix + "CONFIG";
var overridden = prefix + "OVERRIDDEN";
var fromCli = prefix + "CLI";

var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?>
{
[$"env:{fromConfig}"] = "config",
[$"env:{overridden}"] = "config",
})
.Build();

try
{
ProxyConfiguration.SetEnvironmentVariables(configuration, new Dictionary<string, string>
{
[overridden] = "cli",
[fromCli] = "cli",
});

Assert.Equal("config", Environment.GetEnvironmentVariable(fromConfig));
Assert.Equal("cli", Environment.GetEnvironmentVariable(overridden));
Assert.Equal("cli", Environment.GetEnvironmentVariable(fromCli));
}
finally
{
Environment.SetEnvironmentVariable(fromConfig, null);
Environment.SetEnvironmentVariable(overridden, null);
Environment.SetEnvironmentVariable(fromCli, null);
}
}
}
4 changes: 2 additions & 2 deletions DevProxy/Commands/DevProxyCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ private void ConfigureCommand()
{
AllowMultipleArgumentsPerToken = true,
Arity = ArgumentArity.ZeroOrMore,
Description = "Variables to set for the Dev Proxy process",
Description = "Environment variables to set for the Dev Proxy process, in name=value format. Also used to replace {name} placeholders in API spec files used by the MinimalPermissions plugins",
HelpName = "env",
};
envOption.Validators.Add(input =>
Expand All @@ -544,7 +544,7 @@ private void ConfigureCommand()
{
// Split on first '=' only
var parts = envVar.Split('=', 2);
if (parts.Length != 2)
if (parts.Length != 2 || parts[0].Length == 0)
{
input.AddError($"Invalid environment variable format: '{envVar}'. Expected format is 'name=value'.");
return;
Expand Down
30 changes: 30 additions & 0 deletions DevProxy/Commands/DevProxyConfigOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,29 @@ public string? ConfigFile
public OutputFormat? Output => _parseResult?.GetValueOrDefault<OutputFormat?>(DevProxyCommand.OutputOptionName);
public LogLevel? LogLevel => _parseResult?.GetValueOrDefault<LogLevel?>(DevProxyCommand.LogLevelOptionName);

public Dictionary<string, string>? Env => ParseEnv(_parseResult?.GetValueOrDefault<string[]?>(DevProxyCommand.EnvOptionName));

internal static Dictionary<string, string>? ParseEnv(string[]? env)
{
if (env is null || env.Length == 0)
{
return null;
}

var result = new Dictionary<string, string>();
foreach (var e in env)
{
// Split on first '=' only
var parts = e.Split('=', 2);
if (parts.Length == 2 && parts[0].Length > 0)
{
result[parts[0]] = parts[1];
}
}

return result;
}

public List<string>? UrlsToWatch
{
get
Expand Down Expand Up @@ -154,6 +177,12 @@ public DevProxyConfigOptions()
Arity = ArgumentArity.Zero
};

var envOption = new Option<string[]?>(DevProxyCommand.EnvOptionName, "-e")
{
AllowMultipleArgumentsPerToken = true,
Arity = ArgumentArity.ZeroOrMore
};

var options = new List<Option>
{
apiPortOption,
Expand All @@ -166,6 +195,7 @@ public DevProxyConfigOptions()
logLevelOption,
outputOption,
discoverOption,
envOption,
noColorOption
};
this.AddOptions(options.OrderByName());
Expand Down
1 change: 1 addition & 0 deletions DevProxy/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ static WebApplication BuildApplication(DevProxyConfigOptions options)
var builder = WebApplication.CreateBuilder();

_ = builder.Configuration.ConfigureDevProxyConfig(options);
ProxyConfiguration.SetEnvironmentVariables(builder.Configuration, options.Env);
_ = builder.Logging.ConfigureDevProxyLogging(builder.Configuration, options);
var allowedOrigins = ApiSecurity.GetAllowedOrigins(builder.Configuration);
_ = builder.Services.ConfigureDevProxyServices(builder.Configuration, options, allowedOrigins);
Expand Down
18 changes: 18 additions & 0 deletions DevProxy/Proxy/ProxyConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,24 @@ public ProxyConfiguration(IConfigurationRoot configurationRoot)
_configurationRoot.Bind(this);
}

// Sets env values from the config file, followed by --env values so that they take precedence.
internal static void SetEnvironmentVariables(IConfiguration configuration, IReadOnlyDictionary<string, string>? commandLineEnv)
{
var configEnv = configuration.GetSection("env").Get<Dictionary<string, string>>();
foreach (var variables in new IEnumerable<KeyValuePair<string, string>>?[] { configEnv, commandLineEnv })
{
if (variables is null)
{
continue;
}

foreach (var (name, value) in variables)
{
Environment.SetEnvironmentVariable(name, value);
Comment on lines +73 to +75
}
}
}

internal static string GetConfigFilePath(IConfigurationRoot configurationRoot)
{
if (configurationRoot.Providers.FirstOrDefault(p => p is FileConfigurationProvider) is not FileConfigurationProvider provider)
Expand Down
11 changes: 11 additions & 0 deletions schemas/v4.0.0/rc.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@
"type": "boolean",
"description": "Whether to set Dev Proxy as the system proxy."
},
"env": {
"type": "object",
"description": "Environment variables to set for the Dev Proxy process. Values are also used to replace {name} placeholders in API spec files used by the MinimalPermissions plugins. Values passed with --env replace these.",
"propertyNames": {
"minLength": 1,
"pattern": "^[^=]+$"
},
"additionalProperties": {
"type": "string"
}
},
"filterByHeaders": {
"type": "array",
"description": "List of headers to filter requests by. Each object specifies a header name and value.",
Expand Down
Loading