Skip to content

Commit aac2a22

Browse files
committed
changes
2 parents c85a0fa + 4bde692 commit aac2a22

23 files changed

+511
-101
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
},
1414
"args": "${input:funcArgs} --script-root ${input:scriptRootArg}",
1515
"cwd": "${workspaceFolder}/src/Cli/func",
16-
"console": "internalConsole",
16+
"console": "integratedTerminal",
1717
"stopAtEntry": false
1818
},
1919
{

eng/res/msi/funcinstall.wxs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
Languages='1033'
3535
SummaryCodepage='1252' />
3636

37+
<!-- Enable verbose logs -->
38+
<Property Id="MsiLogging" Value="voicewarmupx" />
39+
3740
<MajorUpgrade AllowDowngrades='yes' Schedule='afterInstallInitialize' />
3841

3942
<Media Id='1' Cabinet='funchost.cab' EmbedCab='yes' />
@@ -83,4 +86,4 @@
8386
<WixVariable Id='WixUIBannerBmp' Value='installbanner.bmp' />
8487
<WixVariable Id='WixUIDialogBmp' Value='installdialog.bmp' />
8588
</Product>
86-
</Wix>
89+
</Wix>

release_notes.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1-
# Azure Functions CLI <version>
1+
# Azure Functions CLI 4.1.1
22

33
#### Host Version
44

5-
- Host Version: <version>
6-
- In-Proc Host Version: <version>
5+
- Host Version: 4.1040.300
6+
- In-Proc Host Version: 4.40.100
77

88
#### Changes
99

1010
- Fix dotnet templates installation (#4538)
1111
- Disable diagnostic events in local development by replacing the `IDiagnosticEventRepository` with a `DiagnosticEventNullRepository` (#4542)
1212
- Add `func pack` support for in-proc functions (#4529)
13+
- Update KEDA templates & `kubernetes create` command to correctly use a provided namespace, or use default namespace (#4558)
1314
- Update `func init` to default to the .NET 8 template for in-proc apps (#4557)
15+
- Implement (2 second) graceful timeout period for the CLI shutdown (#4540)
16+
- Overwrite `AZURE_FUNCTIONS_ENVIRONMENT` to `Development` if it is already set (#4563)
17+
- Warn if there is a `JsonException` when parsing the `local.settings.json` file (#4571)
18+
- Enabled verbose logs in MSI by default (#4578)

src/Cli/func/Actions/HelpAction.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,16 +128,22 @@ private void DisplayContextHelp(Context context, Context subContext)
128128
{
129129
if (subContext == Context.None)
130130
{
131-
ColoredConsole
132-
.WriteLine($"{TitleColor("Usage:")} func {context.ToLowerCaseString()} [context] <action> [-/--options]")
133-
.WriteLine();
134131
var contexts = _actionTypes
135132
.Where(a => a.Contexts.Contains(context))
136133
.Select(a => a.SubContexts)
137134
.SelectMany(c => c)
138135
.Where(c => c != Context.None)
139136
.Distinct()
140137
.OrderBy(c => c.ToLowerCaseString());
138+
139+
var hasSubcontexts = contexts.Any();
140+
var usageFormat = hasSubcontexts
141+
? $"{TitleColor("Usage:")} func {context.ToLowerCaseString()} [subcontext] <action> [-/--options]"
142+
: $"{TitleColor("Usage:")} func {context.ToLowerCaseString()} <action> [-/--options]";
143+
144+
ColoredConsole
145+
.WriteLine(usageFormat)
146+
.WriteLine();
141147
DisplayContextsHelp(contexts);
142148
}
143149
else
@@ -178,7 +184,7 @@ private void DisplayGeneralHelp()
178184
.OrderBy(c => c.ToLowerCaseString());
179185
Utilities.PrintVersion();
180186
ColoredConsole
181-
.WriteLine("Usage: func [context] [context] <action> [-/--options]")
187+
.WriteLine("Usage: func [context] <action> [-/--options]")
182188
.WriteLine();
183189
DisplayContextsHelp(contexts);
184190
var actions = _actionTypes.Where(a => a.Contexts.Contains(Context.None));

src/Cli/func/Actions/HostActions/StartHostAction.cs

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ private async Task<IWebHost> BuildWebHost(ScriptApplicationHostOptions hostOptio
273273
.Build();
274274
}
275275

276-
private async Task<IDictionary<string, string>> GetConfigurationSettings(string scriptPath, Uri uri)
276+
internal async Task<IDictionary<string, string>> GetConfigurationSettings(string scriptPath, Uri uri)
277277
{
278278
var settings = _secretsManager.GetSecrets();
279279
settings.Add(Constants.WebsiteHostname, uri.Authority);
@@ -298,9 +298,14 @@ private async Task<IDictionary<string, string>> GetConfigurationSettings(string
298298

299299
await CheckNonOptionalSettings(settings.Union(environment).Union(userSecrets), scriptPath, userSecretsEnabled);
300300

301-
// when running locally in CLI we want the host to run in debug mode
302-
// which optimizes host responsiveness
303-
settings.Add("AZURE_FUNCTIONS_ENVIRONMENT", "Development");
301+
// When running locally in CLI we want the host to run in debug mode which optimizes host responsiveness
302+
// We intentionally override the value of AZURE_FUNCTIONS_ENVIRONMENT to Development if it is already set to something else.
303+
if (settings.TryGetValue("AZURE_FUNCTIONS_ENVIRONMENT", out var oldValue))
304+
{
305+
ColoredConsole.WriteLine(WarningColor($"AZURE_FUNCTIONS_ENVIRONMENT already exists with value '{oldValue}', overriding to 'Development'."));
306+
}
307+
308+
settings["AZURE_FUNCTIONS_ENVIRONMENT"] = "Development";
304309

305310
// Inject the .NET Worker startup hook if debugging the worker
306311
if (DotNetIsolatedDebug != null && DotNetIsolatedDebug.Value)
@@ -442,22 +447,25 @@ public override async Task RunAsync()
442447
var runTask = host.RunAsync();
443448
var hostService = host.Services.GetRequiredService<WebJobsScriptHostService>();
444449

445-
await hostService.DelayUntilHostReady();
446-
447-
var scriptHost = hostService.Services.GetRequiredService<IScriptJobHost>();
448-
var httpOptions = hostService.Services.GetRequiredService<IOptions<HttpOptions>>();
449-
if (scriptHost != null && scriptHost.Functions.Any())
450+
if (hostService.State is not ScriptHostState.Stopping && hostService.State is not ScriptHostState.Stopped)
450451
{
451-
// Checking if in Limelight - it should have a `AzureDevSessionsRemoteHostName` value in local.settings.json.
452-
var forwardedHttpUrl = _secretsManager.GetSecrets().FirstOrDefault(
453-
s => s.Key.Equals(Constants.AzureDevSessionsRemoteHostName, StringComparison.OrdinalIgnoreCase)).Value;
454-
if (forwardedHttpUrl != null)
452+
await hostService.DelayUntilHostReady();
453+
454+
var scriptHost = hostService.Services.GetRequiredService<IScriptJobHost>();
455+
var httpOptions = hostService.Services.GetRequiredService<IOptions<HttpOptions>>();
456+
if (scriptHost != null && scriptHost.Functions.Any())
455457
{
456-
var baseUrl = forwardedHttpUrl.Replace(Constants.AzureDevSessionsPortSuffixPlaceholder, Port.ToString(), StringComparison.OrdinalIgnoreCase);
457-
baseUri = new Uri(baseUrl);
458-
}
458+
// Checking if in Limelight - it should have a `AzureDevSessionsRemoteHostName` value in local.settings.json.
459+
var forwardedHttpUrl = _secretsManager.GetSecrets().FirstOrDefault(
460+
s => s.Key.Equals(Constants.AzureDevSessionsRemoteHostName, StringComparison.OrdinalIgnoreCase)).Value;
461+
if (forwardedHttpUrl != null)
462+
{
463+
var baseUrl = forwardedHttpUrl.Replace(Constants.AzureDevSessionsPortSuffixPlaceholder, Port.ToString(), StringComparison.OrdinalIgnoreCase);
464+
baseUri = new Uri(baseUrl);
465+
}
459466

460-
DisplayFunctionsInfoUtilities.DisplayFunctionsInfo(scriptHost.Functions, httpOptions.Value, baseUri);
467+
DisplayFunctionsInfoUtilities.DisplayFunctionsInfo(scriptHost.Functions, httpOptions.Value, baseUri);
468+
}
461469
}
462470

463471
if (VerboseLogging == null || !VerboseLogging.Value)

src/Cli/func/Actions/KubernetesActions/DeployKedaAction.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ internal class DeployKedaAction : BaseAction
1919

2020
public override ICommandLineParserResult ParseArgs(string[] args)
2121
{
22-
SetFlag<string>("namespace", "Kubernetes namespace to deploy to. Default: Current namespace in Kubernetes config.", ns => Namespace = ns);
22+
SetFlag<string>("namespace", "Kubernetes namespace to deploy to. Default: Current namespace in Kubernetes config if set, otherwise 'default'", ns => Namespace = ns);
2323
SetFlag<KedaVersion>("keda-version", $"Defines the version of KEDA to install. Default: {KedaVersion.V2}. Options are: {KedaVersion.V1} or {KedaVersion.V2}", f => KedaVersion = f);
2424
SetFlag<bool>("dry-run", "Show the deployment template", f => DryRun = f);
2525

@@ -28,7 +28,9 @@ public override ICommandLineParserResult ParseArgs(string[] args)
2828

2929
public override async Task RunAsync()
3030
{
31+
Namespace ??= await KubernetesHelper.GetCurrentNamespaceOrDefault("default");
3132
var kedaDeploymentYaml = KedaHelper.GetKedaDeploymentYaml(Namespace, KedaVersion);
33+
3234
if (DryRun)
3335
{
3436
ColoredConsole.WriteLine(kedaDeploymentYaml);

src/Cli/func/Actions/KubernetesActions/KubernetesDeployAction.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public override ICommandLineParserResult ParseArgs(string[] args)
9595
SetFlag<string>("image-name", "Image to use for the pod deployment and to read functions from", n => ImageName = n);
9696
SetFlag<KedaVersion>("keda-version", $"Defines the version of KEDA to use. Default: {Kubernetes.KEDA.KedaVersion.V2}. Options are: {Kubernetes.KEDA.KedaVersion.V1} or {Kubernetes.KEDA.KedaVersion.V2}", n => KedaVersion = n);
9797
SetFlag<string>("registry", "When set, a docker build is run and the image is pushed to that registry/name. This is mutually exclusive with --image-name. For docker hub, use username.", r => Registry = r);
98-
SetFlag<string>("namespace", "Kubernetes namespace to deploy to. Default: Current namespace in Kubernetes config.", ns => Namespace = ns);
98+
SetFlag<string>("namespace", "Kubernetes namespace to deploy to. Default: Current namespace in Kubernetes config if set, otherwise 'default'.", ns => Namespace = ns);
9999
SetFlag<string>("pull-secret", "The secret holding a private registry credentials", s => PullSecret = s);
100100
SetFlag<int>("polling-interval", "The polling interval for checking non-http triggers. Default: 30 (seconds)", p => PollingInterval = p);
101101
SetFlag<int>("cooldown-period", "The cooldown period for the deployment before scaling back to 0 after all triggers are no longer active. Default: 300 (seconds)", p => CooldownPeriod = p);
@@ -131,6 +131,8 @@ public override ICommandLineParserResult ParseArgs(string[] args)
131131

132132
public override async Task RunAsync()
133133
{
134+
Namespace ??= await KubernetesHelper.GetCurrentNamespaceOrDefault("default");
135+
134136
(var resolvedImageName, var shouldBuild) = await ResolveImageName();
135137
TriggersPayload triggers = null;
136138
if (DryRun)

src/Cli/func/CancelKeyHandler.cs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
namespace Azure.Functions.Cli;
5+
6+
internal static class CancelKeyHandler
7+
{
8+
private static readonly TimeSpan _gracefulShutdownPeriod = TimeSpan.FromSeconds(2);
9+
private static readonly ConsoleCancelEventHandler _handlerDelegate = HandleCancelKeyPress;
10+
private static Action _onShuttingDown;
11+
private static Action _onGracePeriodTimeout;
12+
private static bool _registered = false;
13+
private static bool _shutdownStarted = false;
14+
15+
public static bool Register(Action onShuttingDown, Action onGracePeriodTimeout = null)
16+
{
17+
if (_registered)
18+
{
19+
return false;
20+
}
21+
22+
ArgumentNullException.ThrowIfNull(onShuttingDown, nameof(onShuttingDown));
23+
24+
_onShuttingDown = onShuttingDown;
25+
_onGracePeriodTimeout = onGracePeriodTimeout;
26+
27+
Console.CancelKeyPress += _handlerDelegate;
28+
_registered = true;
29+
return true;
30+
}
31+
32+
internal static void HandleCancelKeyPress(object sender, ConsoleCancelEventArgs e)
33+
{
34+
if (_shutdownStarted)
35+
{
36+
return;
37+
}
38+
39+
_shutdownStarted = true;
40+
_onShuttingDown?.Invoke();
41+
42+
if (_onGracePeriodTimeout is not null)
43+
{
44+
_ = Task.Run(async () =>
45+
{
46+
await Task.Delay(_gracefulShutdownPeriod);
47+
_onGracePeriodTimeout?.Invoke();
48+
});
49+
}
50+
}
51+
52+
// For testing purposes, we need to ensure that the handler can be unregistered properly.
53+
internal static void Unregister()
54+
{
55+
if (_registered)
56+
{
57+
Console.CancelKeyPress -= _handlerDelegate;
58+
_registered = false;
59+
}
60+
61+
_shutdownStarted = false;
62+
_onShuttingDown = null;
63+
_onGracePeriodTimeout = null;
64+
}
65+
}

src/Cli/func/Common/AppSettingsFile.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
// Licensed under the MIT License. See LICENSE in the project root for license information.
33

44
using System.Text;
5+
using Colors.Net;
56
using Newtonsoft.Json;
67
using Newtonsoft.Json.Linq;
8+
using static Azure.Functions.Cli.Common.OutputTheme;
79

810
namespace Azure.Functions.Cli.Common
911
{
@@ -24,8 +26,13 @@ public AppSettingsFile(string filePath)
2426
ConnectionStrings = appSettings.ConnectionStrings;
2527
Host = appSettings.Host;
2628
}
27-
catch
29+
catch (Exception ex)
2830
{
31+
if (ex is JsonException)
32+
{
33+
ColoredConsole.WriteLine(WarningColor($"Failed to read app settings file at '{_filePath}'. Ensure it is a valid JSON file. {ex.Message}"));
34+
}
35+
2936
Values = new Dictionary<string, string>();
3037
ConnectionStrings = new Dictionary<string, JToken>();
3138
IsEncrypted = true;

0 commit comments

Comments
 (0)