private async Task InitFunctionAppProject() { WorkerRuntime workerRuntime; string language = string.Empty; if (Csx) { workerRuntime = Helpers.WorkerRuntime.dotnet; } else { (workerRuntime, language) = ResolveWorkerRuntimeAndLanguage(WorkerRuntime, Language); } TelemetryHelpers.AddCommandEventToDictionary(TelemetryCommandEvents, "WorkerRuntime", workerRuntime.ToString()); if (workerRuntime == Helpers.WorkerRuntime.dotnet && !Csx) { await DotnetHelpers.DeployDotnetProject(Utilities.SanitizeLiteral(Path.GetFileName(Environment.CurrentDirectory), allowed: "-"), Force); } else { bool managedDependenciesOption = ResolveManagedDependencies(workerRuntime, ManagedDependencies); await InitLanguageSpecificArtifacts(workerRuntime, language, managedDependenciesOption); await WriteFiles(); await WriteHostJson(workerRuntime, managedDependenciesOption, ExtensionBundle); await WriteLocalSettingsJson(workerRuntime); } await WriteExtensionsJson(); if (InitSourceControl) { await SetupSourceControl(); } if (InitDocker) { await WriteDockerfile(workerRuntime, Csx); } }
public static async Task RunAsync <T>(string[] args, IContainer container) { var stopWatch = Stopwatch.StartNew(); // This will flush any old telemetry that was saved // We only do this for clients that have not opted out var telemetry = new Telemetry.Telemetry(Guid.NewGuid().ToString()); telemetry.Flush(); var exitCode = ExitCodes.Success; var app = new ConsoleApp(args, typeof(T).Assembly, container); // If all goes well, we will have an action to run. // This action can be an actual action, or just a HelpAction, but this method doesn't care // since HelpAction is still an IAction. try { var action = app.Parse(); if (action != null) { Utilities.PrintUpgradeWarning(); await Task.Delay(TimeSpan.FromSeconds(1)); if (action is IInitializableAction) { var initializableAction = action as IInitializableAction; await initializableAction.Initialize(); } // All Actions are async. No return value is expected from any action. await action.RunAsync(); TelemetryHelpers.UpdateTelemetryEvent(app._telemetryEvent, action.TelemetryCommandEvents); app._telemetryEvent.IsSuccessful = true; } } catch (Exception ex) { if (StaticSettings.IsDebug) { // If CLI is in debug mode, display full call stack. ColoredConsole.Error.WriteLine(ErrorColor(ex.ToString())); } else { ColoredConsole.Error.WriteLine(ErrorColor(ex.Message)); } if (args.Any(a => a.Equals("--pause-on-error", StringComparison.OrdinalIgnoreCase))) { ColoredConsole.Write("Press any to continue...."); Console.ReadKey(true); } app._telemetryEvent.IsSuccessful = false; exitCode = ExitCodes.GeneralError; } finally { stopWatch.Stop(); // Log the event if we did recognize an event if (!string.IsNullOrEmpty(app._telemetryEvent?.CommandName)) { app._telemetryEvent.TimeTaken = stopWatch.ElapsedMilliseconds; TelemetryHelpers.LogEventIfAllowedSafe(telemetry, app._telemetryEvent); } Environment.Exit(exitCode); } }
public async override Task RunAsync() { if (Console.IsOutputRedirected || Console.IsInputRedirected) { if (string.IsNullOrEmpty(TemplateName) || string.IsNullOrEmpty(FunctionName)) { ColoredConsole .Error .WriteLine(ErrorColor("Running with stdin\\stdout redirected. Command must specify --template, and --name explicitly.")) .WriteLine(ErrorColor("See 'func help function' for more details")); return; } } var workerRuntime = GlobalCoreToolsSettings.CurrentWorkerRuntimeOrNone; var templates = await _templatesManager.Templates; if (workerRuntime != WorkerRuntime.None && !string.IsNullOrWhiteSpace(Language)) { // validate var workerRuntimeSelected = WorkerRuntimeLanguageHelper.NormalizeWorkerRuntime(Language); if (workerRuntime != workerRuntimeSelected) { throw new CliException("Selected language doesn't match worker set in local.settings.json." + $"Selected worker is: {workerRuntime} and selected language is: {workerRuntimeSelected}"); } } else if (string.IsNullOrWhiteSpace(Language)) { if (workerRuntime == WorkerRuntime.None) { SelectionMenuHelper.DisplaySelectionWizardPrompt("language"); Language = SelectionMenuHelper.DisplaySelectionWizard(templates.Select(t => t.Metadata.Language).Where(l => !l.Equals("python", StringComparison.OrdinalIgnoreCase)).Distinct()); workerRuntime = WorkerRuntimeLanguageHelper.SetWorkerRuntime(_secretsManager, Language); } else if (workerRuntime != WorkerRuntime.dotnet || Csx) { var languages = WorkerRuntimeLanguageHelper.LanguagesForWorker(workerRuntime); var displayList = templates .Select(t => t.Metadata.Language) .Where(l => languages.Contains(l, StringComparer.OrdinalIgnoreCase)) .Distinct() .ToArray(); if (displayList.Length == 1) { Language = displayList.First(); } else if (!InferAndUpdateLanguage(workerRuntime)) { SelectionMenuHelper.DisplaySelectionWizardPrompt("language"); Language = SelectionMenuHelper.DisplaySelectionWizard(displayList); } } } else if (!string.IsNullOrWhiteSpace(Language)) { workerRuntime = WorkerRuntimeLanguageHelper.SetWorkerRuntime(_secretsManager, Language); } if (workerRuntime == WorkerRuntime.dotnet && !Csx) { SelectionMenuHelper.DisplaySelectionWizardPrompt("template"); TemplateName = TemplateName ?? SelectionMenuHelper.DisplaySelectionWizard(DotnetHelpers.GetTemplates()); ColoredConsole.Write("Function name: "); FunctionName = FunctionName ?? Console.ReadLine(); ColoredConsole.WriteLine(FunctionName); var namespaceStr = Path.GetFileName(Environment.CurrentDirectory); await DotnetHelpers.DeployDotnetFunction(TemplateName.Replace(" ", string.Empty), Utilities.SanitizeClassName(FunctionName), Utilities.SanitizeNameSpace(namespaceStr)); } else { SelectionMenuHelper.DisplaySelectionWizardPrompt("template"); string templateLanguage; try { templateLanguage = WorkerRuntimeLanguageHelper.NormalizeLanguage(Language); } catch (Exception) { // Ideally this should never happen. templateLanguage = WorkerRuntimeLanguageHelper.GetDefaultTemplateLanguageFromWorker(workerRuntime); } TelemetryHelpers.AddCommandEventToDictionary(TelemetryCommandEvents, "language", templateLanguage); TemplateName = TemplateName ?? SelectionMenuHelper.DisplaySelectionWizard(templates.Where(t => t.Metadata.Language.Equals(templateLanguage, StringComparison.OrdinalIgnoreCase)).Select(t => t.Metadata.Name).Distinct()); ColoredConsole.WriteLine(TitleColor(TemplateName)); var template = templates.FirstOrDefault(t => Utilities.EqualsIgnoreCaseAndSpace(t.Metadata.Name, TemplateName) && t.Metadata.Language.Equals(templateLanguage, StringComparison.OrdinalIgnoreCase)); if (template == null) { TelemetryHelpers.AddCommandEventToDictionary(TelemetryCommandEvents, "template", "N/A"); throw new CliException($"Can't find template \"{TemplateName}\" in \"{Language}\""); } else { TelemetryHelpers.AddCommandEventToDictionary(TelemetryCommandEvents, "template", TemplateName); var extensionBundleManager = ExtensionBundleHelper.GetExtensionBundleManager(); if (template.Metadata.Extensions != null && !extensionBundleManager.IsExtensionBundleConfigured() && !CommandChecker.CommandExists("dotnet")) { throw new CliException($"The {template.Metadata.Name} template has extensions. {Constants.Errors.ExtensionsNeedDotnet}"); } ColoredConsole.Write($"Function name: [{template.Metadata.DefaultFunctionName}] "); FunctionName = FunctionName ?? Console.ReadLine(); FunctionName = string.IsNullOrEmpty(FunctionName) ? template.Metadata.DefaultFunctionName : FunctionName; await _templatesManager.Deploy(FunctionName, template); PerformPostDeployTasks(FunctionName, Language); } } ColoredConsole.WriteLine($"The function \"{FunctionName}\" was created successfully from the \"{TemplateName}\" template."); }
/// <summary> /// This method parses _args into an IAction. /// </summary> internal IAction Parse() { if (_args.Length == 1 && _versionArgs.Any(va => _args[0].Replace("-", "").Equals(va, StringComparison.OrdinalIgnoreCase))) { _telemetryEvent.CommandName = "version"; _telemetryEvent.IActionName = null; _telemetryEvent.Parameters = new List <string>(); _telemetryEvent.IsSuccessful = true; ColoredConsole.WriteLine($"{Constants.CliVersion}"); return(null); } // If there is no args are passed, display help. // If args are passed and any it matched any of the strings in _helpArgs with a "-" then display help. // Otherwise, continue parsing. if (_args.Length == 0 || (_args.Length == 1 && _helpArgs.Any(ha => _args[0].Replace("-", "").Equals(ha, StringComparison.OrdinalIgnoreCase))) ) { _telemetryEvent.CommandName = "help"; _telemetryEvent.IActionName = typeof(HelpAction).Name; _telemetryEvent.Parameters = new List <string>(); return(new HelpAction(_actionAttributes, CreateAction)); } bool isHelp = false; var argsToParse = Enumerable.Empty <string>(); // this supports the format: // `func help <context: optional> <subContext: optional> <action: optional>` // but help has to be the first word. So `func azure help` for example doesn't work // but `func help azure` should work. if (_args.First().Equals("help", StringComparison.OrdinalIgnoreCase)) { argsToParse = _args.Skip(1); isHelp = true; } else { // This is for passing --help anywhere in the command line. var argsHelpIntersection = _args .Where(a => a.StartsWith("-")) .Select(a => a.ToLowerInvariant().Replace("-", "")) .Intersect(_helpArgs) .ToArray(); isHelp = argsHelpIntersection.Any(); argsToParse = isHelp ? _args.Where(a => !a.StartsWith("-") || argsHelpIntersection.Contains(a.Replace("-", "").ToLowerInvariant())) : _args; } // We'll need to grab context arg: string, subcontext arg: string, action arg: string var contextStr = string.Empty; var subContextStr = string.Empty; var actionStr = string.Empty; // These start out as None, but if contextStr and subContextStr hold a value, they'll // get parsed into these var context = Context.None; var subContext = Context.None; // If isHelp, skip one and parse the rest of the command as usual. var argsStack = new Stack <string>(argsToParse.Reverse()); // 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. contextStr = argsStack.Peek(); // Use this to collect all the invoking commands such as - "host start" or "azure functionapp publish" var invokeCommand = new StringBuilder(); if (Enum.TryParse(contextStr, true, out context)) { // It is a valid context, so pop it out of the stack. argsStack.Pop(); invokeCommand.Append(contextStr); if (argsStack.Any()) { // We still have items in the stack, do the same again for subContext. // This means we only support 2 levels of contexts only. Main, and Sub. // There is currently no way to declaratively specify any more. // If we ever need more than 2 contexts, we should switch to a more generic mechanism. subContextStr = argsStack.Peek(); if (Enum.TryParse(subContextStr, true, out subContext)) { argsStack.Pop(); invokeCommand.Append(" "); invokeCommand.Append(subContextStr); } } } if (argsStack.Any()) { // If there are still more items in the stack, then it's an actionStr actionStr = argsStack.Pop(); } if (string.IsNullOrEmpty(actionStr) || isHelp) { // It's ok to log invoke command here because it only contains the // strings we were able to match with context / subcontext. var invokedCommand = invokeCommand.ToString(); _telemetryEvent.CommandName = string.IsNullOrEmpty(invokedCommand) ? "help" : invokedCommand; _telemetryEvent.IActionName = typeof(HelpAction).Name; _telemetryEvent.Parameters = new List <string>(); // If this wasn't a help command, actionStr was empty or null implying a parseError. _telemetryEvent.ParseError = !isHelp; // At this point we have all we need to create an IAction: // context // subContext // action // However, if isHelp is true, then display help for that context. // Action Name is ignored with help since we don't have action specific help yet. // There is no need so far for action specific help since general context help displays // the help for all the actions in that context anyway. return(new HelpAction(_actionAttributes, CreateAction, contextStr, subContextStr)); } // Find the matching action type. // We expect to find 1 and only 1 IAction that matches all 3 (context, subContext, action) var actionType = _actionAttributes .Where(a => a.Attribute.Name.Equals(actionStr, StringComparison.OrdinalIgnoreCase) && a.Attribute.Context == context && a.Attribute.SubContext == subContext) .SingleOrDefault(); // If none is found, display help passing in all the info we have right now. if (actionType == null) { // If we did not find the action, // we cannot log any invoked keywords as they may have PII _telemetryEvent.CommandName = "help"; _telemetryEvent.IActionName = typeof(HelpAction).Name; _telemetryEvent.Parameters = new List <string>(); _telemetryEvent.ParseError = true; return(new HelpAction(_actionAttributes, CreateAction, contextStr, subContextStr)); } // If we are here that means actionStr is a legit action if (invokeCommand.Length > 0) { invokeCommand.Append(" "); } invokeCommand.Append(actionStr); // Create the IAction var action = CreateAction(actionType.Type); // Grab whatever is left in the stack of args into an array. // This will be passed into the action as actions can optionally take args for their options. var args = argsStack.ToArray(); try { // Give the action a change to parse its args. var parseResult = action.ParseArgs(args); if (parseResult.HasErrors) { // If we matched the action, we can log the invoke command _telemetryEvent.CommandName = invokeCommand.ToString(); _telemetryEvent.IActionName = typeof(HelpAction).Name; _telemetryEvent.Parameters = new List <string>(); _telemetryEvent.ParseError = true; // There was an error with the args, pass it to the HelpAction. return(new HelpAction(_actionAttributes, CreateAction, action, parseResult)); } else { _telemetryEvent.CommandName = invokeCommand.ToString(); _telemetryEvent.IActionName = action.GetType().Name; _telemetryEvent.Parameters = TelemetryHelpers.GetCommandsFromCommandLineOptions(action.MatchedOptions); // Action is ready to run. return(action); } } catch (CliArgumentsException ex) { // TODO: we can probably display help here as well. // This happens for actions that expect an ordered untyped options. ColoredConsole.Error.WriteLine(ex.Message); // If we matched the action, we can log the invoke command _telemetryEvent.CommandName = invokeCommand.ToString(); _telemetryEvent.IActionName = action.GetType().Name; _telemetryEvent.Parameters = new List <string>(); _telemetryEvent.ParseError = true; _telemetryEvent.IsSuccessful = false; return(null); } }