Skip to content

Commit

Permalink
Check environment for 'FUNCTIONS_WORKER_RUNTIME' on start (#4219)
Browse files Browse the repository at this point in the history
  • Loading branch information
liliankasem authored Jan 8, 2025
1 parent 8961bad commit c09a203
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 139 deletions.
12 changes: 6 additions & 6 deletions src/Azure.Functions.Cli/Actions/HostActions/StartHostAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,7 @@ private void ValidateAndBuildHostJsonConfigurationIfFileExists(ScriptApplication
private async Task PreRunConditions()
{
EnsureWorkerRuntimeIsSet();

if (GlobalCoreToolsSettings.CurrentWorkerRuntime == WorkerRuntime.python)
{
var pythonVersion = await PythonHelpers.GetEnvironmentPythonVersion();
Expand Down Expand Up @@ -813,23 +814,22 @@ internal static bool ConnectionExists(IEnumerable<KeyValuePair<string, string>>

private void EnsureWorkerRuntimeIsSet()
{
var workerRuntimeSettingValue = _secretsManager.GetSecrets().FirstOrDefault(s => s.Key.Equals(Constants.FunctionsWorkerRuntime, StringComparison.OrdinalIgnoreCase)).Value;
if (workerRuntimeSettingValue is not null)
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(Constants.FunctionsWorkerRuntime))
|| _secretsManager.GetSecrets().Any(s => s.Key.Equals(Constants.FunctionsWorkerRuntime, StringComparison.OrdinalIgnoreCase)))
{
return;
}

if (GlobalCoreToolsSettings.CurrentWorkerRuntimeOrNone == WorkerRuntime.None)
if (GlobalCoreToolsSettings.CurrentWorkerRuntimeOrNone is WorkerRuntime.None)
{
SelectionMenuHelper.DisplaySelectionWizardPrompt("worker runtime");
IDictionary<WorkerRuntime, string> workerRuntimeToDisplayString = WorkerRuntimeLanguageHelper.GetWorkerToDisplayStrings();
string workerRuntimeDisplay = SelectionMenuHelper.DisplaySelectionWizard(workerRuntimeToDisplayString.Values);
GlobalCoreToolsSettings.CurrentWorkerRuntime = workerRuntimeToDisplayString.FirstOrDefault(wr => wr.Value.Equals(workerRuntimeDisplay)).Key;
}

var workerRuntime = WorkerRuntimeLanguageHelper.GetRuntimeMoniker(GlobalCoreToolsSettings.CurrentWorkerRuntime);
_secretsManager.SetSecret(Constants.FunctionsWorkerRuntime, workerRuntime);
ColoredConsole.WriteLine(WarningColor($"'{workerRuntime}' has been set in your local.settings.json"));
// Update local.settings.json
WorkerRuntimeLanguageHelper.SetWorkerRuntime(_secretsManager, GlobalCoreToolsSettings.CurrentWorkerRuntime.ToString());
}
}
}
16 changes: 10 additions & 6 deletions src/Azure.Functions.Cli/Helpers/WorkerRuntimeLanguageHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,9 @@ public static IEnumerable<string> LanguagesForWorker(WorkerRuntime worker)

public static WorkerRuntime GetCurrentWorkerRuntimeLanguage(ISecretsManager secretsManager)
{
var setting = secretsManager.GetSecrets().FirstOrDefault(s => s.Key.Equals(Constants.FunctionsWorkerRuntime, StringComparison.OrdinalIgnoreCase)).Value;
var setting = Environment.GetEnvironmentVariable(Constants.FunctionsWorkerRuntime)
?? secretsManager.GetSecrets().FirstOrDefault(s => s.Key.Equals(Constants.FunctionsWorkerRuntime, StringComparison.OrdinalIgnoreCase)).Value;

try
{
return NormalizeWorkerRuntime(setting);
Expand All @@ -179,14 +181,16 @@ public static WorkerRuntime GetCurrentWorkerRuntimeLanguage(ISecretsManager secr

internal static WorkerRuntime SetWorkerRuntime(ISecretsManager secretsManager, string language)
{
var worker = NormalizeWorkerRuntime(language);
var workerRuntime = NormalizeWorkerRuntime(language);
var runtimeMoniker = GetRuntimeMoniker(workerRuntime);

secretsManager.SetSecret(Constants.FunctionsWorkerRuntime, runtimeMoniker);

secretsManager.SetSecret(Constants.FunctionsWorkerRuntime, worker.ToString());
ColoredConsole
.WriteLine(WarningColor("Starting from 2.0.1-beta.26 it's required to set a language for your project in your settings"))
.WriteLine(WarningColor($"'{worker}' has been set in your local.settings.json"));
.WriteLine(WarningColor("Starting from 2.0.1-beta.26 it's required to set a language for your project in your settings."))
.WriteLine(WarningColor($"Worker runtime '{runtimeMoniker}' has been set in '{SecretsManager.AppSettingsFilePath}'."));

return worker;
return workerRuntime;
}

public static string GetDefaultTemplateLanguageFromWorker(WorkerRuntime worker)
Expand Down
184 changes: 57 additions & 127 deletions test/Azure.Functions.Cli.Tests/E2E/StartTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1708,144 +1708,74 @@ await CliTester.Run(new RunConfiguration[]
}

[Theory]
[InlineData("dotnet-isolated")]
[InlineData("node")]
public async Task Start_MissingLocalSettingsJson_SuccessfulFunctionExecution(string language)
[InlineData("dotnet-isolated", "--dotnet-isolated", "HttpTriggerFunc: [GET,POST] http://localhost:", true, false)] // Runtime parameter set (dni), successful startup & invocation
[InlineData("node", "--node", "HttpTriggerFunc: [GET,POST] http://localhost:", true, false)] // Runtime parameter set (node), successful startup & invocation
[InlineData("dotnet", "--worker-runtime None", $"Use the up/down arrow keys to select a worker runtime:", false, false)] // Runtime parameter set to None, worker runtime prompt displayed
[InlineData("dotnet", "", $"Use the up/down arrow keys to select a worker runtime:", false, false)] // Runtime parameter not provided, worker runtime prompt displayed
[InlineData("dotnet-isolated", "", "HttpTriggerFunc: [GET,POST] http://localhost:", true, true)] // Runtime value is set via environment variable, successful startup & invocation
public async Task Start_MissingLocalSettingsJson_BehavesAsExpected(string language, string runtimeParameter, string expectedOutput, bool invokeFunction, bool setRuntimeViaEnvironment)
{
await CliTester.Run(new RunConfiguration[]
try
{
new RunConfiguration
if (setRuntimeViaEnvironment)
{
Commands = new[]
{
$"init . --worker-runtime {language}",
$"new --template Httptrigger --name HttpTriggerFunc",
},
CommandTimeout = TimeSpan.FromSeconds(300),
},
new RunConfiguration
Environment.SetEnvironmentVariable("FUNCTIONS_WORKER_RUNTIME", "dotnet-isolated");
}

await CliTester.Run(new RunConfiguration[]
{
PreTest = (workingDir) =>
new RunConfiguration
{
var LocalSettingsJson = Path.Combine(workingDir, "local.settings.json");
File.Delete(LocalSettingsJson);
},
Commands = new[]
{
$"start --{language} --port {_funcHostPort}",
},
ExpectExit = false,
OutputContains = new[]
{
$"local.settings.json",
"Functions:",
$"HttpTriggerFunc: [GET,POST] http://localhost:{_funcHostPort}/api/HttpTriggerFunc"
},
OutputDoesntContain = new string[]
{
"Initializing function HTTP routes"
Commands = new[]
{
$"init . --worker-runtime {language}",
$"new --template Httptrigger --name HttpTriggerFunc",
},
CommandTimeout = TimeSpan.FromSeconds(300),
},
Test = async (_, p,_) =>
new RunConfiguration
{
using (var client = new HttpClient() { BaseAddress = new Uri($"http://localhost:{_funcHostPort}/") })
PreTest = (workingDir) =>
{
(await WaitUntilReady(client)).Should().BeTrue(because: _serverNotReady);
var response = await client.GetAsync("/api/HttpTriggerFunc?name=Test");
response.StatusCode.Should().Be(HttpStatusCode.OK);
await Task.Delay(TimeSpan.FromSeconds(2));
p.Kill();
var localSettingsJson = Path.Combine(workingDir, "local.settings.json");
File.Delete(localSettingsJson);
},
Commands = new[]
{
$"start {runtimeParameter} --port {_funcHostPort}",
},
ExpectExit = false,
OutputContains = new[]
{
expectedOutput
},
Test = async (_, p,_) =>
{
if (invokeFunction)
{
using (var client = new HttpClient() { BaseAddress = new Uri($"http://localhost:{_funcHostPort}/") })
{
(await WaitUntilReady(client)).Should().BeTrue(because: _serverNotReady);
var response = await client.GetAsync("/api/HttpTriggerFunc?name=Test");
response.StatusCode.Should().Be(HttpStatusCode.OK);
await Task.Delay(TimeSpan.FromSeconds(2));
p.Kill();
}
}
else
{
await Task.Delay(TimeSpan.FromSeconds(2));
p.Kill();
}

}
}
}
}, _output);
}

[Fact]
public async Task Start_MissingLocalSettingsJson_Runtime_None_HandledAsExpected()
{
await CliTester.Run(new RunConfiguration[]
{
new RunConfiguration
{
Commands = new[]
{
$"init . --worker-runtime dotnet",
$"new --template Httptrigger --name HttpTriggerFunc",
},
CommandTimeout = TimeSpan.FromSeconds(300),
},
new RunConfiguration
{
PreTest = (workingDir) =>
{
var LocalSettingsJson = Path.Combine(workingDir, "local.settings.json");
File.Delete(LocalSettingsJson);
},
Commands = new[]
{
$"start --worker-runtime None --port {_funcHostPort}",
},
ExpectExit = false,
OutputContains = new[]
{
$"Use the up/down arrow keys to select a worker runtime:"
},
OutputDoesntContain = new string[]
{
"Initializing function HTTP routes"
},
Test = async (_, p,_) =>
{
await Task.Delay(TimeSpan.FromSeconds(2));
p.Kill();
}
}
}, _output);

}

[Fact]
public async Task Start_MissingLocalSettingsJson_Runtime_NotProvided_HandledAsExpected()
{
await CliTester.Run(new RunConfiguration[]
}, _output);
}
finally
{
new RunConfiguration
{
Commands = new[]
{
$"init . --worker-runtime dotnet",
$"new --template Httptrigger --name HttpTriggerFunc",
},
CommandTimeout = TimeSpan.FromSeconds(300),
},
new RunConfiguration
{
PreTest = (workingDir) =>
{
var LocalSettingsJson = Path.Combine(workingDir, "local.settings.json");
File.Delete(LocalSettingsJson);
},
Commands = new[]
{
$"start --port {_funcHostPort}",
},
ExpectExit = false,
OutputContains = new[]
{
$"Use the up/down arrow keys to select a worker runtime:"
},
OutputDoesntContain = new string[]
{
"Initializing function HTTP routes"
},
Test = async (_, p,_) =>
{
await Task.Delay(TimeSpan.FromSeconds(2));
p.Kill();
}
}
}, _output);

Environment.SetEnvironmentVariable("FUNCTIONS_WORKER_RUNTIME", null);
}
}

private async Task<bool> WaitUntilReady(HttpClient client)
Expand Down

0 comments on commit c09a203

Please sign in to comment.