Skip to content
Merged
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
2 changes: 2 additions & 0 deletions release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@
#### Changes

- Added end-of-life warnings for all runtime versions during `func azure functionapp publish`. (#4760)
- Reduced console output noise by moving informational messages to verbose logging. (#4768)
- Fixed an issue where creating an MCP Tool trigger function would fail with "Unknown template 'McpToolTrigger'" error. (#4768)
18 changes: 3 additions & 15 deletions src/Cli/func/Actions/HostActions/StartHostAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,6 @@ public override ICommandLineParserResult ParseArgs(string[] args)
.WithDescription("A space separated list of functions to load.")
.Callback(f => EnabledFunctions = f);

Parser
.Setup<bool>("verbose")
.WithDescription("When false, hides system logs other than warnings and errors.")
.SetDefault(false)
.Callback(v => VerboseLogging = v);

Parser
.Setup<string>("user-log-level")
.WithDescription("Sets the minimum log level for displaying user logs. Valid values: Trace, Debug, Information, Warning, Error, Critical, None. This does not affect system logs.")
Expand Down Expand Up @@ -185,16 +179,10 @@ public override ICommandLineParserResult ParseArgs(string[] args)
.WithDescription($"If provided, determines which version of the host to start. Allowed values are '{DotnetConstants.InProc6HostRuntime}', '{DotnetConstants.InProc8HostRuntime}', and 'default' (which runs the out-of-process host).")
.Callback(startHostFromRuntime => HostRuntime = startHostFromRuntime);

var parserResult = base.ParseArgs(args);
bool verboseLoggingArgExists = parserResult.UnMatchedOptions.Any(o => o.LongName.Equals("verbose", StringComparison.OrdinalIgnoreCase));

// Input args do not contain --verbose flag
if (!VerboseLogging.Value && verboseLoggingArgExists)
{
VerboseLogging = null;
}
// Verbose logging now follows the global --verbose flag
VerboseLogging = GlobalCoreToolsSettings.IsVerbose;

return parserResult;
return base.ParseArgs(args);
}

private async Task<IWebHost> BuildWebHost(ScriptApplicationHostOptions hostOptions, Uri listenAddress, Uri baseAddress, X509Certificate2 certificate)
Expand Down
7 changes: 6 additions & 1 deletion src/Cli/func/Common/SecretsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Azure.Functions.Cli.Interfaces;
using Colors.Net;
using Microsoft.Azure.WebJobs.Script;
using static Azure.Functions.Cli.Common.OutputTheme;
using static Colors.Net.StringStaticMethods;

namespace Azure.Functions.Cli.Common
Expand All @@ -27,7 +28,11 @@ public static string AppSettingsFilePath
});
var secretsFilePath = Path.Combine(rootPath, secretsFile);

ColoredConsole.WriteLine(DarkGray($"'{secretsFile}' found in root directory ({rootPath})."));
if (GlobalCoreToolsSettings.IsVerbose)
{
ColoredConsole.WriteLine(VerboseColor($"'{secretsFile}' found in root directory ({rootPath})."));
}

return secretsFilePath;
}
}
Expand Down
25 changes: 21 additions & 4 deletions src/Cli/func/Common/TemplatesManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Azure.Functions.Cli.Interfaces;
using Colors.Net;
using Newtonsoft.Json;
using static Azure.Functions.Cli.Common.OutputTheme;

namespace Azure.Functions.Cli.Common
{
Expand Down Expand Up @@ -177,7 +178,11 @@ private async Task DeployNewNodeProgrammingModel(string functionName, string fil
foreach (var filePath in fileList.Keys)
{
RemoveFileIfExists(filePath);
ColoredConsole.WriteLine($"Creating a new file {filePath}");
if (GlobalCoreToolsSettings.IsVerbose)
{
ColoredConsole.WriteLine(VerboseColor($"Creating a new file {filePath}"));
}

await FileSystemHelpers.WriteAllTextToFileAsync(filePath, fileList[filePath]);
}
}
Expand Down Expand Up @@ -249,7 +254,11 @@ private async Task DeployTraditionalModel(string name, Template template)
foreach (var file in template.Files.Where(kv => !kv.Key.EndsWith(".dat")))
{
var filePath = Path.Combine(path, file.Key);
ColoredConsole.WriteLine($"Writing {filePath}");
if (GlobalCoreToolsSettings.IsVerbose)
{
ColoredConsole.WriteLine(VerboseColor($"Writing {filePath}"));
}

await FileSystemHelpers.WriteAllTextToFileAsync(filePath, file.Value);
}

Expand Down Expand Up @@ -371,7 +380,11 @@ private async Task WriteFunctionBody(NewTemplate template, TemplateAction action
var filePath = Path.Combine(Environment.CurrentDirectory, fileName);
if (!FileSystemHelpers.FileExists(filePath))
{
ColoredConsole.WriteLine($"Creating a new file {filePath}");
if (GlobalCoreToolsSettings.IsVerbose)
{
ColoredConsole.WriteLine(VerboseColor($"Creating a new file {filePath}"));
}

await FileSystemHelpers.WriteAllTextToFileAsync(filePath, sourceContent);
}
else
Expand All @@ -382,7 +395,11 @@ private async Task WriteFunctionBody(NewTemplate template, TemplateAction action
}

var fileContent = await FileSystemHelpers.ReadAllTextFromFileAsync(filePath);
ColoredConsole.WriteLine($"Appending to {filePath}");
if (GlobalCoreToolsSettings.IsVerbose)
{
ColoredConsole.WriteLine(VerboseColor($"Appending to {filePath}"));
}

fileContent = $"{fileContent}{Environment.NewLine}{Environment.NewLine}{sourceContent}";

// Update the file.
Expand Down
77 changes: 53 additions & 24 deletions src/Cli/func/ConsoleApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@ internal class ConsoleApp
private readonly IContainer _container;
private readonly string[] _args;
private readonly IEnumerable<TypeAttributePair> _actionAttributes;
private readonly string[] _helpArgs = new[] { "help", "h", "?" };
private readonly string[] _helpArgs = ["help", "h", "?"];
private readonly TelemetryEvent _telemetryEvent;

// Global options that should be filtered out during action parsing
private static readonly HashSet<string> _globalFlags = new(StringComparer.OrdinalIgnoreCase) { "--version", "--verbose" };
private static readonly HashSet<string> _globalOptionsWithValues = new(StringComparer.OrdinalIgnoreCase) { "--script-root", "--prefix" };

internal ConsoleApp(string[] args, Assembly assembly, IContainer container)
{
_args = args;
Expand Down Expand Up @@ -247,7 +251,7 @@ internal IAction Parse()

argsToParse = isHelp
? _args.Where(a => !a.StartsWith("-"))
: _args;
: FilterOutGlobalOptions(_args);
}

// We'll need to grab context arg: string, subcontext arg: string, action arg: string
Expand All @@ -263,6 +267,15 @@ internal IAction Parse()
// If isHelp, skip one and parse the rest of the command as usual.
var argsStack = new Stack<string>(argsToParse.Reverse());

// If no arguments remain after filtering, show general help
if (!argsStack.Any())
{
_telemetryEvent.CommandName = "help";
_telemetryEvent.IActionName = typeof(HelpAction).Name;
_telemetryEvent.Parameters = new List<string>();
return new HelpAction(_actionAttributes, CreateAction);
}

// Grab the first string, but don't pop it off the stack.
// If it's indeed a valid context, will remove it later.
// Otherwise, it could be just an action. Actions are allowed not to have contexts.
Expand Down Expand Up @@ -405,44 +418,60 @@ internal IAction Parse()
}
}

/// <summary>
/// Filters out global options and their values from the argument list.
/// Global flags: --version, --verbose
/// Global options with values: --script-root, --prefix.
/// </summary>
private static IEnumerable<string> FilterOutGlobalOptions(string[] args)
{
for (int i = 0; i < args.Length; i++)
{
var arg = args[i];

if (_globalFlags.Contains(arg))
{
continue;
}

if (_globalOptionsWithValues.Contains(arg))
{
i++; // Skip the next argument (the value)
continue;
}

yield return arg;
}
}

/// <summary>
/// This method will update Environment.CurrentDirectory
/// if there is a --script-root or a --prefix provided on the commandline.
/// </summary>
/// <param name="args">args to check for --prefix or --script-root.</param>
private void UpdateCurrentDirectory(string[] args)
{
// assume index of -1 means the string is not there
int index = -1;
for (var i = 0; i < args.Length; i++)
{
if (args[i].Equals("--script-root", StringComparison.OrdinalIgnoreCase)
|| args[i].Equals("--prefix", StringComparison.OrdinalIgnoreCase))
if (_globalOptionsWithValues.Contains(args[i]) && i + 1 < args.Length)
{
// update the index to point to the following entry in args
// which should contain the path for a prefix
index = i + 1;
_telemetryEvent.PrefixOrScriptRoot = true;
break;
}
}

// make sure index still in the array
if (index != -1 && index < args.Length)
{
// Path.Combine takes care of checking if the path is full path or not.
// For example, Path.Combine(@"C:\temp", @"dir\dir") => "C:\temp\dir\dir"
// Path.Combine(@"C:\temp", @"C:\Windows") => "C:\Windows"
// Path.Combine("/usr/bin", "dir/dir") => "/usr/bin/dir/dir"
// Path.Combine("/usr/bin", "/opt/dir") => "/opt/dir"
var path = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, args[index]));
if (FileSystemHelpers.DirectoryExists(path))
{
Environment.CurrentDirectory = path;
}
else
{
throw new CliException($"\"{path}\" doesn't exist.");
var path = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, args[i + 1]));
if (FileSystemHelpers.DirectoryExists(path))
{
Environment.CurrentDirectory = path;
}
else
{
throw new CliException($"\"{path}\" doesn't exist.");
}

return;
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/Cli/func/Helpers/DotnetHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,11 @@ await TemplateOperationAsync(

public static async Task DeployDotnetFunction(string templateName, string functionName, string namespaceStr, string language, WorkerRuntime workerRuntime, AuthorizationLevel? httpAuthorizationLevel = null)
{
ColoredConsole.WriteLine($"{Environment.NewLine}Creating dotnet function...");
if (GlobalCoreToolsSettings.IsVerbose)
{
ColoredConsole.WriteLine(VerboseColor($"{Environment.NewLine}Creating dotnet function..."));
}

await TemplateOperationAsync(
async () =>
{
Expand Down Expand Up @@ -183,6 +187,7 @@ await TemplateOperationAsync(
"iothubtrigger" => "iothub",
"kafkatrigger" => "kafka",
"kafkaoutput" => "kafkao",
"mcptooltrigger" => "mcptooltrigger",
"queuetrigger" => "queue",
"sendgrid" => "sendgrid",
"servicebusqueuetrigger" => "squeue",
Expand Down
5 changes: 5 additions & 0 deletions src/Cli/func/Helpers/GlobalCoreToolsSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ public static class GlobalCoreToolsSettings
{
private static WorkerRuntime _currentWorkerRuntime;
private static bool _isHelpRunning;
private static bool _isVerbose;

public static bool IsHelpRunning => _isHelpRunning;

public static bool IsVerbose => _isVerbose;

public static ProgrammingModel? CurrentProgrammingModel { get; set; }

public static WorkerRuntime CurrentWorkerRuntime
Expand Down Expand Up @@ -48,6 +51,8 @@ public static WorkerRuntime CurrentWorkerRuntimeOrNone

public static void Init(ISecretsManager secretsManager, string[] args)
{
_isVerbose = args.Contains("--verbose");

try
{
if (args.Contains("--csharp"))
Expand Down
5 changes: 4 additions & 1 deletion src/Cli/func/Helpers/PythonHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,10 @@ public static void AssertPythonVersion(WorkerLanguageVersionInfo pythonVersion,
return;
}

ColoredConsole.WriteLine(AdditionalInfoColor($"Found Python version {pythonVersion.Version} ({pythonVersion.ExecutablePath})."));
if (GlobalCoreToolsSettings.IsVerbose)
{
ColoredConsole.WriteLine(VerboseColor($"Found Python version {pythonVersion.Version} ({pythonVersion.ExecutablePath})."));
}

// Python 3.[7|8|9|10|11|12] (supported)
if (IsVersionSupported(pythonVersion))
Expand Down
8 changes: 6 additions & 2 deletions src/Cli/func/Helpers/WorkerRuntimeLanguageHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,12 +177,16 @@ public static IEnumerable<string> LanguagesForWorker(WorkerRuntime worker)
public static WorkerRuntime GetCurrentWorkerRuntimeLanguage(ISecretsManager secretsManager, bool refreshSecrets = false)
{
var setting = Environment.GetEnvironmentVariable(Constants.FunctionsWorkerRuntime)
?? secretsManager.GetSecrets(refreshSecrets).FirstOrDefault(s => s.Key.Equals(Constants.FunctionsWorkerRuntime, StringComparison.OrdinalIgnoreCase)).Value;
?? secretsManager.GetSecrets(refreshSecrets)?.FirstOrDefault(s => s.Key.Equals(Constants.FunctionsWorkerRuntime, StringComparison.OrdinalIgnoreCase)).Value;

try
{
WorkerRuntime workerRuntime = NormalizeWorkerRuntime(setting);
ColoredConsole.WriteLine($"Resolving worker runtime to '{GetRuntimeMoniker(workerRuntime)}'.");
if (GlobalCoreToolsSettings.IsVerbose)
{
ColoredConsole.WriteLine(VerboseColor($"Resolving worker runtime to '{GetRuntimeMoniker(workerRuntime)}'."));
}

return workerRuntime;
}
catch
Expand Down
2 changes: 1 addition & 1 deletion test/Cli/Func.E2ETests/Commands/FuncStart/AuthTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public async Task Start_DotnetIsolated_EnableAuthFeature(
};

// Build command arguments based on enableAuth parameter
var commandArgs = new List<string> { "start", "--verbose", "--port", port.ToString() };
var commandArgs = new List<string> { "--port", port.ToString(), "--verbose" };
if (enableAuth)
{
commandArgs.Add("--enableAuth");
Expand Down