public async Task <int> LocalRunAsync(CommandSettings command, CancellationToken token) { Trace.Info(nameof(LocalRunAsync)); // Warn preview. _term.WriteLine("This command is currently in preview. The interface and behavior will change in a future version."); if (!command.Unattended) { _term.WriteLine("Press Enter to continue."); _term.ReadLine(); } HostContext.RunMode = RunMode.Local; // Resolve the YAML file path. string ymlFile = command.GetYml(); if (string.IsNullOrEmpty(ymlFile)) { string[] ymlFiles = Directory.GetFiles(Directory.GetCurrentDirectory()) .Where((string filePath) => { return(filePath.EndsWith(".yml", IOUtil.FilePathStringComparison)); }) .ToArray(); if (ymlFiles.Length > 1) { throw new Exception($"More than one .yml file exists in the current directory. Specify which file to use via the --'{Constants.Agent.CommandLine.Args.Yml}' command line argument."); } ymlFile = ymlFiles.FirstOrDefault(); } if (string.IsNullOrEmpty(ymlFile)) { throw new Exception($"Unable to find a .yml file in the current directory. Specify which file to use via the --'{Constants.Agent.CommandLine.Args.Yml}' command line argument."); } // Load the YAML file. var parseOptions = new ParseOptions { MaxFiles = 10, MustacheEvaluationMaxResultLength = 512 * 1024, // 512k string length MustacheEvaluationTimeout = TimeSpan.FromSeconds(10), MustacheMaxDepth = 5, }; var pipelineParser = new PipelineParser(new PipelineTraceWriter(), new PipelineFileProvider(), parseOptions); if (command.WhatIf) { pipelineParser.DeserializeAndSerialize( defaultRoot: Directory.GetCurrentDirectory(), path: ymlFile, mustacheContext: null, cancellationToken: HostContext.AgentShutdownToken); return(Constants.Agent.ReturnCode.Success); } YamlContracts.Process process = pipelineParser.LoadInternal( defaultRoot: Directory.GetCurrentDirectory(), path: ymlFile, mustacheContext: null, cancellationToken: HostContext.AgentShutdownToken); ArgUtil.NotNull(process, nameof(process)); // Verify the current directory is the root of a git repo. string repoDirectory = Directory.GetCurrentDirectory(); if (!Directory.Exists(Path.Combine(repoDirectory, ".git"))) { throw new Exception("Unable to run the build locally. The command must be executed from the root directory of a local git repository."); } // Verify at least one phase was found. if (process.Phases == null || process.Phases.Count == 0) { throw new Exception($"No phases or steps were discovered from the file: '{ymlFile}'"); } // Filter the phases. string phaseName = command.GetPhase(); if (!string.IsNullOrEmpty(phaseName)) { process.Phases = process.Phases .Cast <YamlContracts.Phase>() .Where(x => string.Equals(x.Name, phaseName, StringComparison.OrdinalIgnoreCase)) .Cast <YamlContracts.IPhase>() .ToList(); if (process.Phases.Count == 0) { throw new Exception($"Phase '{phaseName}' not found."); } } // Verify a phase was specified if more than one phase was found. if (process.Phases.Count > 1) { throw new Exception($"More than one phase was discovered. Use the --{Constants.Agent.CommandLine.Args.Phase} argument to specify a phase."); } // Get the matrix. var phase = process.Phases[0] as YamlContracts.Phase; var queueTarget = phase.Target as QueueTarget; // Filter to a specific matrix. string matrixName = command.GetMatrix(); if (!string.IsNullOrEmpty(matrixName)) { if (queueTarget?.Matrix != null) { queueTarget.Matrix = queueTarget.Matrix.Keys .Where(x => string.Equals(x, matrixName, StringComparison.OrdinalIgnoreCase)) .ToDictionary(keySelector: x => x, elementSelector: x => queueTarget.Matrix[x]); } if (queueTarget?.Matrix == null || queueTarget.Matrix.Count == 0) { throw new Exception($"Job configuration matrix '{matrixName}' not found."); } } // Verify a matrix was specified if more than one matrix was found. if (queueTarget?.Matrix != null && queueTarget.Matrix.Count > 1) { throw new Exception($"More than one job configuration matrix was discovered. Use the --{Constants.Agent.CommandLine.Args.Matrix} argument to specify a matrix."); } // Get the URL - required if missing tasks. string url = command.GetUrl(suppressPromptIfEmpty: true); if (string.IsNullOrEmpty(url)) { if (!TestAllTasksCached(process, token)) { url = command.GetUrl(suppressPromptIfEmpty: false); } } if (!string.IsNullOrEmpty(url)) { // Initialize and store the HTTP client. var credentialManager = HostContext.GetService <ICredentialManager>(); // Get the auth type. On premise defaults to negotiate (Kerberos with fallback to NTLM). // Hosted defaults to PAT authentication. string defaultAuthType = UrlUtil.IsHosted(url) ? Constants.Configuration.PAT : (Constants.Agent.Platform == Constants.OSPlatform.Windows ? Constants.Configuration.Integrated : Constants.Configuration.Negotiate); string authType = command.GetAuth(defaultValue: defaultAuthType); ICredentialProvider provider = credentialManager.GetCredentialProvider(authType); provider.EnsureCredential(HostContext, command, url); _taskStore.HttpClient = new TaskAgentHttpClient(new Uri(url), provider.GetVssCredentials(HostContext)); } var configStore = HostContext.GetService <IConfigurationStore>(); AgentSettings settings = configStore.GetSettings(); // Create job message. JobInfo job = (await ConvertToJobMessagesAsync(process, repoDirectory, token)).Single(); IJobDispatcher jobDispatcher = null; try { jobDispatcher = HostContext.CreateService <IJobDispatcher>(); job.RequestMessage.Environment.Variables[Constants.Variables.Agent.RunMode] = RunMode.Local.ToString(); jobDispatcher.Run(job.RequestMessage); Task jobDispatch = jobDispatcher.WaitAsync(token); if (!Task.WaitAll(new[] { jobDispatch }, job.Timeout)) { jobDispatcher.Cancel(job.CancelMessage); // Finish waiting on the job dispatch task. The call to jobDispatcher.WaitAsync dequeues // the job dispatch task. In the cancel flow, we need to continue awaiting the task instance // (queue is now empty). await jobDispatch; } // Translate the job result to an agent return code. TaskResult jobResult = jobDispatcher.GetLocalRunJobResult(job.RequestMessage); switch (jobResult) { case TaskResult.Succeeded: case TaskResult.SucceededWithIssues: return(Constants.Agent.ReturnCode.Success); default: return(Constants.Agent.ReturnCode.TerminatedError); } } finally { if (jobDispatcher != null) { await jobDispatcher.ShutdownAsync(); } } }
public async Task <int> RunAsync(CommandSettings command, CancellationToken token) { Trace.Info(nameof(RunAsync)); var configStore = HostContext.GetService <IConfigurationStore>(); AgentSettings settings = configStore.GetSettings(); // Store the HTTP client. // todo: fix in master to allow URL to be empty and then rebase on master. const string DefaultUrl = "http://127.0.0.1/local-runner-default-url"; string url = command.GetUrl(DefaultUrl); if (!string.Equals(url, DefaultUrl, StringComparison.Ordinal)) { var credentialManager = HostContext.GetService <ICredentialManager>(); string authType = command.GetAuth(defaultValue: Constants.Configuration.Integrated); ICredentialProvider provider = credentialManager.GetCredentialProvider(authType); provider.EnsureCredential(HostContext, command, url); _httpClient = new TaskAgentHttpClient(new Uri(url), provider.GetVssCredentials(HostContext)); } // Load the YAML file. string yamlFile = command.GetYaml(); ArgUtil.File(yamlFile, nameof(yamlFile)); var parseOptions = new ParseOptions { MaxFiles = 10, MustacheEvaluationMaxResultLength = 512 * 1024, // 512k string length MustacheEvaluationTimeout = TimeSpan.FromSeconds(10), MustacheMaxDepth = 5, }; var pipelineParser = new PipelineParser(new PipelineTraceWriter(), new PipelineFileProvider(), parseOptions); Pipelines.Process process = pipelineParser.Load( defaultRoot: Directory.GetCurrentDirectory(), path: yamlFile, mustacheContext: null, cancellationToken: HostContext.AgentShutdownToken); ArgUtil.NotNull(process, nameof(process)); if (command.WhatIf) { return(Constants.Agent.ReturnCode.Success); } // Create job message. IJobDispatcher jobDispatcher = null; try { jobDispatcher = HostContext.CreateService <IJobDispatcher>(); foreach (JobInfo job in await ConvertToJobMessagesAsync(process, token)) { job.RequestMessage.Environment.Variables[Constants.Variables.Agent.RunMode] = RunMode.Local.ToString(); jobDispatcher.Run(job.RequestMessage); Task jobDispatch = jobDispatcher.WaitAsync(token); if (!Task.WaitAll(new[] { jobDispatch }, job.Timeout)) { jobDispatcher.Cancel(job.CancelMessage); // Finish waiting on the same job dispatch task. The first call to WaitAsync dequeues // the dispatch task and then proceeds to wait on it. So we need to continue awaiting // the task instance (queue is now empty). await jobDispatch; } } } finally { if (jobDispatcher != null) { await jobDispatcher.ShutdownAsync(); } } return(Constants.Agent.ReturnCode.Success); }
public async Task <int> LocalRunAsync(CommandSettings command, CancellationToken token) { Trace.Info(nameof(LocalRunAsync)); // Warn preview. _term.WriteLine("This command is currently in preview. The interface and behavior will change in a future version."); if (!command.Unattended) { _term.WriteLine("Press Enter to continue."); _term.ReadLine(); } HostContext.RunMode = RunMode.Local; // Resolve the YAML file path. string ymlFile = command.GetYml(); if (string.IsNullOrEmpty(ymlFile)) { string[] ymlFiles = Directory.GetFiles(Directory.GetCurrentDirectory()) .Where((string filePath) => { return(filePath.EndsWith(".yml", IOUtil.FilePathStringComparison)); }) .ToArray(); if (ymlFiles.Length > 1) { throw new Exception($"More than one .yml file exists in the current directory. Specify which file to use via the '{Constants.Agent.CommandLine.Args.Yml}' command line argument."); } ymlFile = ymlFiles.FirstOrDefault(); } if (string.IsNullOrEmpty(ymlFile)) { throw new Exception($"Unable to find a .yml file in the current directory. Specify which file to use via the '{Constants.Agent.CommandLine.Args.Yml}' command line argument."); } // Load the YAML file. var parseOptions = new ParseOptions { MaxFiles = 10, MustacheEvaluationMaxResultLength = 512 * 1024, // 512k string length MustacheEvaluationTimeout = TimeSpan.FromSeconds(10), MustacheMaxDepth = 5, }; var pipelineParser = new PipelineParser(new PipelineTraceWriter(), new PipelineFileProvider(), parseOptions); Pipelines.Process process = pipelineParser.Load( defaultRoot: Directory.GetCurrentDirectory(), path: ymlFile, mustacheContext: null, cancellationToken: HostContext.AgentShutdownToken); ArgUtil.NotNull(process, nameof(process)); if (command.WhatIf) { return(Constants.Agent.ReturnCode.Success); } // Verify the current directory is the root of a git repo. string repoDirectory = Directory.GetCurrentDirectory(); if (!Directory.Exists(Path.Combine(repoDirectory, ".git"))) { throw new Exception("Unable to run the build locally. The command must be executed from the root directory of a local git repository."); } // Get the URL - required if missing tasks. string url = command.GetUrl(suppressPromptIfEmpty: true); if (string.IsNullOrEmpty(url)) { if (!TestAllTasksCached(process, token)) { url = command.GetUrl(suppressPromptIfEmpty: false); } } if (!string.IsNullOrEmpty(url)) { // Initialize and store the HTTP client. var credentialManager = HostContext.GetService <ICredentialManager>(); // Get the auth type. On premise defaults to negotiate (Kerberos with fallback to NTLM). // Hosted defaults to PAT authentication. string defaultAuthType = UrlUtil.IsHosted(url) ? Constants.Configuration.PAT : (Constants.Agent.Platform == Constants.OSPlatform.Windows ? Constants.Configuration.Integrated : Constants.Configuration.Negotiate); string authType = command.GetAuth(defaultValue: defaultAuthType); ICredentialProvider provider = credentialManager.GetCredentialProvider(authType); provider.EnsureCredential(HostContext, command, url); _taskStore.HttpClient = new TaskAgentHttpClient(new Uri(url), provider.GetVssCredentials(HostContext)); } var configStore = HostContext.GetService <IConfigurationStore>(); AgentSettings settings = configStore.GetSettings(); // Create job message. IJobDispatcher jobDispatcher = null; try { jobDispatcher = HostContext.CreateService <IJobDispatcher>(); foreach (JobInfo job in await ConvertToJobMessagesAsync(process, repoDirectory, token)) { job.RequestMessage.Environment.Variables[Constants.Variables.Agent.RunMode] = RunMode.Local.ToString(); jobDispatcher.Run(job.RequestMessage); Task jobDispatch = jobDispatcher.WaitAsync(token); if (!Task.WaitAll(new[] { jobDispatch }, job.Timeout)) { jobDispatcher.Cancel(job.CancelMessage); // Finish waiting on the same job dispatch task. The first call to WaitAsync dequeues // the dispatch task and then proceeds to wait on it. So we need to continue awaiting // the task instance (queue is now empty). await jobDispatch; } } } finally { if (jobDispatcher != null) { await jobDispatcher.ShutdownAsync(); } } return(Constants.Agent.ReturnCode.Success); }
public void PromptsWhenInvalid() { using (TestHostContext hc = CreateTestContext()) { // Arrange. var command = new CommandSettings(hc, args: new string[] { "--url", "notValid" }); _promptManager .Setup(x => x.ReadValue( Constants.Agent.CommandLine.Args.Url, // argName StringUtil.Loc("ServerUrl"), // description false, // secret string.Empty, // defaultValue Validators.ServerUrlValidator, // validator false)) // unattended .Returns("some url"); // Act. string actual = command.GetUrl(); // Assert. Assert.Equal("some url", actual); } }