Exemple #1
0
        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);
            }
        }
Exemple #3
0
        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);
            }
        }