public void GetsArg() { using (TestHostContext hc = CreateTestContext()) { // Arrange. var command = new CommandSettings(hc, args: new string[] { "--agent", "some agent" }); // Act. string actual = command.GetAgent(); // Assert. Assert.Equal("some agent", actual); } }
public void GetsCommandUnconfigure() { using (TestHostContext hc = CreateTestContext()) { // Arrange. var command = new CommandSettings(hc, args: new string[] { "remove" }); // Act. bool actual = command.Unconfigure; // Assert. Assert.True(actual); } }
public void GetsCommandRun() { using (TestHostContext hc = CreateTestContext()) { // Arrange. var command = new CommandSettings(hc, args: new string[] { "run" }); // Act. bool actual = command.Run; // Assert. Assert.True(actual); } }
public void WindowsServiceControlManagerShouldInstallService() { using (var tc = CreateTestContext()) { var serviceControlManager = new WindowsServiceControlManager(); serviceControlManager.Initialize(tc); var agentSettings = new AgentSettings { ServerUrl = "http://server.name", AgentName = "myagent" }; var command = new CommandSettings( tc, new[] { "--windowslogonaccount", _expectedLogonAccount, "--windowslogonpassword", _expectedLogonPassword, "--unattended" }); serviceControlManager.ConfigureService(agentSettings, command); Assert.Equal("vstsagent.server.myagent", serviceControlManager.ServiceName); Assert.Equal("VSTS Agent (server.myagent)", serviceControlManager.ServiceDisplayName); } }
//process 2 new job messages, and one cancel message public async void TestRunAsync() { using (var hc = new TestHostContext(this)) using (var tokenSource = new CancellationTokenSource()) { //Arrange var agent = new Agent.Listener.Agent(); agent.TokenSource = tokenSource; hc.SetSingleton<IConfigurationManager>(_configurationManager.Object); hc.SetSingleton<IJobNotification>(_jobNotification.Object); hc.SetSingleton<IMessageListener>(_messageListener.Object); hc.SetSingleton<IPromptManager>(_promptManager.Object); hc.SetSingleton<IAgentServer>(_agentServer.Object); agent.Initialize(hc); var settings = new AgentSettings { PoolId = 43242 }; var taskAgentSession = new TaskAgentSession(); //we use reflection to achieve this, because "set" is internal PropertyInfo sessionIdProperty = taskAgentSession.GetType().GetProperty("SessionId", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); Assert.NotNull(sessionIdProperty); sessionIdProperty.SetValue(taskAgentSession, Guid.NewGuid()); var message = new TaskAgentMessage() { Body = JsonUtility.ToString(CreateJobRequestMessage("job1")), MessageId = 4234, MessageType = JobRequestMessage.MessageType }; var messages = new Queue<TaskAgentMessage>(); messages.Enqueue(message); var signalWorkerComplete = new SemaphoreSlim(0, 1); _configurationManager.Setup(x => x.LoadSettings()) .Returns(settings); _configurationManager.Setup(x => x.IsConfigured()) .Returns(true); _configurationManager.Setup(x => x.EnsureConfiguredAsync(It.IsAny<CommandSettings>())) .Returns(Task.CompletedTask); _messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>())) .Returns(Task.FromResult<bool>(true)); _messageListener.Setup(x => x.Session) .Returns(taskAgentSession); _messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>())) .Returns(async () => { if (0 == messages.Count) { signalWorkerComplete.Release(); await Task.Delay(2000, tokenSource.Token); } return messages.Dequeue(); }); _messageListener.Setup(x => x.DeleteSessionAsync()) .Returns(Task.CompletedTask); _jobDispatcher.Setup(x => x.Run(It.IsAny<JobRequestMessage>())) .Callback(()=> { }); _agentServer.Setup(x => x.DeleteAgentMessageAsync(settings.PoolId, message.MessageId, taskAgentSession.SessionId, It.IsAny<CancellationToken>())) .Returns((Int32 poolId, Int64 messageId, Guid sessionId, CancellationToken cancellationToken) => { return Task.CompletedTask; }); _jobNotification.Setup(x => x.StartClient(It.IsAny<String>(), It.IsAny<CancellationToken>())) .Callback(() => { }); hc.EnqueueInstance<IJobDispatcher>(_jobDispatcher.Object); //Act var command = new CommandSettings(hc, new string[0]); Task agentTask = agent.ExecuteCommand(command); //Assert //wait for the agent to run one job if (!await signalWorkerComplete.WaitAsync(2000)) { Assert.True(false, $"{nameof(_messageListener.Object.GetNextMessageAsync)} was not invoked."); } else { //Act tokenSource.Cancel(); //stop Agent //Assert Task[] taskToWait2 = { agentTask, Task.Delay(2000) }; //wait for the Agent to exit await Task.WhenAny(taskToWait2); Assert.True(agentTask.IsCompleted, $"{nameof(agent.ExecuteCommand)} timed out."); Assert.True(!agentTask.IsFaulted, agentTask.Exception?.ToString()); Assert.True(agentTask.IsCanceled); _jobDispatcher.Verify(x => x.Run(It.IsAny<JobRequestMessage>()), Times.Once(), $"{nameof(_jobDispatcher.Object.Run)} was not invoked."); _messageListener.Verify(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()), Times.AtLeastOnce()); _messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), Times.Once()); _messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once()); _agentServer.Verify(x => x.DeleteAgentMessageAsync(settings.PoolId, message.MessageId, taskAgentSession.SessionId, It.IsAny<CancellationToken>()), Times.AtLeastOnce()); } } }
// Return code definition: (this will be used by service host to determine whether it will re-launch agent.listener) // 0: Agent exit // 1: Terminate failure // 2: Retriable failure // 3: Exit for self update private async static Task <int> MainAsync(IHostContext context, string[] args) { Tracing trace = context.GetTrace("AgentProcess"); trace.Info($"Agent package {BuildConstants.AgentPackage.PackageName}."); trace.Info($"Running on {PlatformUtil.HostOS} ({PlatformUtil.HostArchitecture})."); trace.Info($"RuntimeInformation: {RuntimeInformation.OSDescription}."); context.WritePerfCounter("AgentProcessStarted"); var terminal = context.GetService <ITerminal>(); // TODO: check that the right supporting tools are available for this platform // (replaces the check for build platform vs runtime platform) try { trace.Info($"Version: {BuildConstants.AgentPackage.Version}"); trace.Info($"Commit: {BuildConstants.Source.CommitHash}"); trace.Info($"Culture: {CultureInfo.CurrentCulture.Name}"); trace.Info($"UI Culture: {CultureInfo.CurrentUICulture.Name}"); // Validate directory permissions. string agentDirectory = context.GetDirectory(WellKnownDirectory.Root); trace.Info($"Validating directory permissions for: '{agentDirectory}'"); try { IOUtil.ValidateExecutePermission(agentDirectory); } catch (Exception e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.TerminatedError); } if (PlatformUtil.RunningOnWindows) { // Validate PowerShell 3.0 or higher is installed. var powerShellExeUtil = context.GetService <IPowerShellExeUtil>(); try { powerShellExeUtil.GetPath(); } catch (Exception e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.TerminatedError); } // Validate .NET Framework 4.5 or higher is installed. if (!NetFrameworkUtil.Test(new Version(4, 5), trace)) { terminal.WriteError(StringUtil.Loc("MinimumNetFramework")); // warn only, like configurationmanager.cs does. this enables windows edition with just .netcore to work } } // Add environment variables from .env file string envFile = Path.Combine(context.GetDirectory(WellKnownDirectory.Root), ".env"); if (File.Exists(envFile)) { var envContents = File.ReadAllLines(envFile); foreach (var env in envContents) { if (!string.IsNullOrEmpty(env) && env.IndexOf('=') > 0) { string envKey = env.Substring(0, env.IndexOf('=')); string envValue = env.Substring(env.IndexOf('=') + 1); Environment.SetEnvironmentVariable(envKey, envValue); } } } // Parse the command line args. var command = new CommandSettings(context, args, new SystemEnvironment()); trace.Info("Arguments parsed"); // Print any Parse Errros if (command.ParseErrors?.Any() == true) { List <string> errorStr = new List <string>(); foreach (var error in command.ParseErrors) { if (error is TokenError tokenError) { errorStr.Add(tokenError.Token); } else { // Unknown type of error dump to log terminal.WriteError(StringUtil.Loc("ErrorOccurred", error.Tag)); } } terminal.WriteError( StringUtil.Loc("UnrecognizedCmdArgs", string.Join(", ", errorStr))); } // Defer to the Agent class to execute the command. IAgent agent = context.GetService <IAgent>(); try { return(await agent.ExecuteCommand(command)); } catch (OperationCanceledException) when(context.AgentShutdownToken.IsCancellationRequested) { trace.Info("Agent execution been cancelled."); return(Constants.Agent.ReturnCode.Success); } catch (NonRetryableException e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.TerminatedError); } } catch (Exception e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.RetryableError); } }
public async Task <int> ExecuteCommand(CommandSettings command) { try { var proxyConfig = HostContext.GetService <IProxyConfiguration>(); proxyConfig.ApplyProxySettings(); _inConfigStage = true; _completedCommand.Reset(); _term.CancelKeyPress += CtrlCHandler; //register a SIGTERM handler HostContext.Unloading += Agent_Unloading; // TODO Unit test to cover this logic Trace.Info(nameof(ExecuteCommand)); var configManager = HostContext.GetService <IConfigurationManager>(); // command is not required, if no command it just starts and/or configures if not configured // TODO: Invalid config prints usage if (command.Help) { PrintUsage(); return(Constants.Agent.ReturnCode.Success); } if (command.Version) { _term.WriteLine(Constants.Agent.Version); return(Constants.Agent.ReturnCode.Success); } if (command.Commit) { _term.WriteLine(BuildConstants.Source.CommitHash); return(Constants.Agent.ReturnCode.Success); } // Configure agent prompt for args if not supplied // Unattend configure mode will not prompt for args if not supplied and error on any missing or invalid value. if (command.Configure) { try { await configManager.ConfigureAsync(command); return(Constants.Agent.ReturnCode.Success); } catch (Exception ex) { Trace.Error(ex); _term.WriteError(ex.Message); return(Constants.Agent.ReturnCode.TerminatedError); } } // Unconfigure, remove config files, service and exit if (command.Unconfigure) { try { await configManager.UnconfigureAsync(command); return(Constants.Agent.ReturnCode.Success); } catch (Exception ex) { Trace.Error(ex); _term.WriteError(ex.Message); return(Constants.Agent.ReturnCode.TerminatedError); } } _inConfigStage = false; AgentSettings settings = configManager.LoadSettings(); bool runAsService = configManager.IsServiceConfigured(); // Run agent //if (command.Run) // this line is current break machine provisioner. //{ // Error if agent not configured. if (!configManager.IsConfigured()) { _term.WriteError(StringUtil.Loc("AgentIsNotConfigured")); PrintUsage(); return(Constants.Agent.ReturnCode.TerminatedError); } // Run the agent interactively or as service Trace.Verbose($"Run as service: '{runAsService}'"); return(await RunAsync(TokenSource.Token, settings, runAsService)); //} // #if OS_WINDOWS // // this code is for migrated .net windows agent that running as windows service. // // leave the code as is untill we have a real plan for auto-migration. // if (runAsService && configManager.IsConfigured() && File.Exists(Path.Combine(IOUtil.GetBinPath(), "VsoAgentService.exe"))) // { // // The old .net windows servicehost doesn't pass correct args while invoke Agent.Listener.exe // // When we detect the agent is a migrated .net windows agent, we will just run the agent.listener.exe even the servicehost doesn't pass correct args. // Trace.Verbose($"Run the agent for compat reason."); // return await RunAsync(TokenSource.Token, settings, runAsService); // } // #endif // Trace.Info("Doesn't match any existing command option, print usage."); // PrintUsage(); // return Constants.Agent.ReturnCode.TerminatedError; } finally { _term.CancelKeyPress -= CtrlCHandler; HostContext.Unloading -= Agent_Unloading; _completedCommand.Set(); } }
public async Task <int> ExecuteCommand(CommandSettings command) { try { var agentWebProxy = HostContext.GetService <IVstsAgentWebProxy>(); var agentCertManager = HostContext.GetService <IAgentCertificateManager>(); VssUtil.InitializeVssClientSettings(HostContext.UserAgent, agentWebProxy.WebProxy, agentCertManager.VssClientCertificateManager); _inConfigStage = true; _completedCommand.Reset(); _term.CancelKeyPress += CtrlCHandler; //register a SIGTERM handler HostContext.Unloading += Agent_Unloading; // TODO Unit test to cover this logic Trace.Info(nameof(ExecuteCommand)); var configManager = HostContext.GetService <IConfigurationManager>(); // command is not required, if no command it just starts if configured // TODO: Invalid config prints usage if (command.Help) { PrintUsage(command); return(Constants.Agent.ReturnCode.Success); } if (command.Version) { _term.WriteLine(Constants.Agent.Version); return(Constants.Agent.ReturnCode.Success); } if (command.Commit) { _term.WriteLine(BuildConstants.Source.CommitHash); return(Constants.Agent.ReturnCode.Success); } // Configure agent prompt for args if not supplied // Unattend configure mode will not prompt for args if not supplied and error on any missing or invalid value. if (command.Configure) { try { await configManager.ConfigureAsync(command); return(Constants.Agent.ReturnCode.Success); } catch (Exception ex) { Trace.Error(ex); _term.WriteError(ex.Message); return(Constants.Agent.ReturnCode.TerminatedError); } } // remove config files, remove service, and exit if (command.Remove) { try { await configManager.UnconfigureAsync(command); return(Constants.Agent.ReturnCode.Success); } catch (Exception ex) { Trace.Error(ex); _term.WriteError(ex.Message); return(Constants.Agent.ReturnCode.TerminatedError); } } _inConfigStage = false; AgentSettings settings = configManager.LoadSettings(); var store = HostContext.GetService <IConfigurationStore>(); bool configuredAsService = store.IsServiceConfigured(); // Run agent //if (command.Run) // this line is current break machine provisioner. //{ // Error if agent not configured. if (!configManager.IsConfigured()) { _term.WriteError(StringUtil.Loc("AgentIsNotConfigured")); PrintUsage(command); return(Constants.Agent.ReturnCode.TerminatedError); } Trace.Verbose($"Configured as service: '{configuredAsService}'"); //Get the startup type of the agent i.e., autostartup, service, manual StartupType startType; var startupTypeAsString = command.GetStartupType(); if (string.IsNullOrEmpty(startupTypeAsString) && configuredAsService) { // We need try our best to make the startup type accurate // The problem is coming from agent autoupgrade, which result an old version service host binary but a newer version agent binary // At that time the servicehost won't pass --startuptype to agent.listener while the agent is actually running as service. // We will guess the startup type only when the agent is configured as service and the guess will based on whether STDOUT/STDERR/STDIN been redirect or not Trace.Info($"Try determine agent startup type base on console redirects."); startType = (Console.IsErrorRedirected && Console.IsInputRedirected && Console.IsOutputRedirected) ? StartupType.Service : StartupType.Manual; } else { if (!Enum.TryParse(startupTypeAsString, true, out startType)) { Trace.Info($"Could not parse the argument value '{startupTypeAsString}' for StartupType. Defaulting to {StartupType.Manual}"); startType = StartupType.Manual; } } Trace.Info($"Set agent startup type - {startType}"); HostContext.StartupType = startType; #if OS_WINDOWS if (store.IsAutoLogonConfigured()) { if (HostContext.StartupType != StartupType.Service) { Trace.Info($"Autologon is configured on the machine, dumping all the autologon related registry settings"); var autoLogonRegManager = HostContext.GetService <IAutoLogonRegistryManager>(); autoLogonRegManager.DumpAutoLogonRegistrySettings(); } else { Trace.Info($"Autologon is configured on the machine but current Agent.Listner.exe is launched from the windows service"); } } #endif // Run the agent interactively or as service return(await RunAsync(settings)); } finally { _term.CancelKeyPress -= CtrlCHandler; HostContext.Unloading -= Agent_Unloading; _completedCommand.Set(); } }
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); } }
public void PromptsForWindowsLogonPassword() { using (TestHostContext hc = CreateTestContext()) { // Arrange. var command = new CommandSettings(hc, args: new string[0]); string accountName = "somewindowsaccount"; _promptManager .Setup(x => x.ReadValue( Constants.Agent.CommandLine.Args.WindowsLogonPassword, // argName StringUtil.Loc("WindowsLogonPasswordDescription", accountName), // description true, // secret string.Empty, // defaultValue Validators.NonEmptyValidator, // validator false)) // unattended .Returns("some windows logon password"); // Act. string actual = command.GetWindowsLogonPassword(accountName); // Assert. Assert.Equal("some windows logon password", actual); } }
public void GetsFlagRunAsService() { using (TestHostContext hc = CreateTestContext()) { // Arrange. var command = new CommandSettings(hc, args: new string[] { "--runasservice" }); // Act. bool actual = command.GetRunAsService(); // Assert. Assert.True(actual); } }
public void GetsFlagNoStart() { using (TestHostContext hc = CreateTestContext()) { // Arrange. var command = new CommandSettings(hc, args: new string[] { "--nostart" }); // Act. bool actual = command.NoStart; // Assert. Assert.True(actual); } }
// Return code definition: (this will be used by service host to determine whether it will re-launch agent.listener) // 0: Agent exit // 1: Terminate failure // 2: Retriable failure // 3: Exit for self update public async static Task <int> MainAsync(string[] args) { using (HostContext context = new HostContext("Agent")) { s_trace = context.GetTrace("AgentProcess"); s_trace.Info($"Agent is built for {Constants.Agent.Platform} - {BuildConstants.AgentPackage.PackageName}."); s_trace.Info($"RuntimeInformation: {RuntimeInformation.OSDescription}."); // Validate the binaries intended for one OS are not running on a different OS. switch (Constants.Agent.Platform) { case Constants.OSPlatform.Linux: if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { Console.WriteLine(StringUtil.Loc("NotLinux")); return(Constants.Agent.ReturnCode.TerminatedError); } break; case Constants.OSPlatform.OSX: if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { Console.WriteLine(StringUtil.Loc("NotOSX")); return(Constants.Agent.ReturnCode.TerminatedError); } break; case Constants.OSPlatform.Windows: if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { Console.WriteLine(StringUtil.Loc("NotWindows")); return(Constants.Agent.ReturnCode.TerminatedError); } break; default: Console.WriteLine(StringUtil.Loc("PlatformNotSupport", RuntimeInformation.OSDescription, Constants.Agent.Platform.ToString())); return(Constants.Agent.ReturnCode.TerminatedError); } int rc = Constants.Agent.ReturnCode.Success; try { s_trace.Info($"Version: {Constants.Agent.Version}"); s_trace.Info($"Commit: {BuildConstants.Source.CommitHash}"); // // TODO (bryanmac): Need VsoAgent.exe compat shim for SCM // That shim will also provide a compat arg parse // and translate / to -- etc... // // Parse the command line args. var command = new CommandSettings(context, args); s_trace.Info("Arguments parsed"); // Defer to the Agent class to execute the command. IAgent agent = context.GetService <IAgent>(); using (agent.TokenSource = new CancellationTokenSource()) { try { rc = await agent.ExecuteCommand(command); } catch (OperationCanceledException) when(agent.TokenSource.IsCancellationRequested) { s_trace.Info("Agent execution been cancelled."); } } } catch (Exception e) { Console.Error.WriteLine(StringUtil.Format("An error occured. {0}", e.Message)); s_trace.Error(e); rc = Constants.Agent.ReturnCode.RetryableError; } return(rc); } }
// Return code definition: (this will be used by service host to determine whether it will re-launch agent.listener) // 0: Agent exit // 1: Terminate failure // 2: Retriable failure // 3: Exit for self update public async static Task <int> MainAsync(IHostContext context, string[] args) { Tracing trace = context.GetTrace("AgentProcess"); trace.Info($"Agent is built for {Constants.Agent.Platform} - {BuildConstants.AgentPackage.PackageName}."); trace.Info($"RuntimeInformation: {RuntimeInformation.OSDescription}."); var terminal = context.GetService <ITerminal>(); // Validate the binaries intended for one OS are not running on a different OS. switch (Constants.Agent.Platform) { case Constants.OSPlatform.Linux: if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { terminal.WriteLine(StringUtil.Loc("NotLinux")); return(Constants.Agent.ReturnCode.TerminatedError); } break; case Constants.OSPlatform.OSX: if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { terminal.WriteLine(StringUtil.Loc("NotOSX")); return(Constants.Agent.ReturnCode.TerminatedError); } break; case Constants.OSPlatform.Windows: if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { terminal.WriteLine(StringUtil.Loc("NotWindows")); return(Constants.Agent.ReturnCode.TerminatedError); } break; default: terminal.WriteLine(StringUtil.Loc("PlatformNotSupport", RuntimeInformation.OSDescription, Constants.Agent.Platform.ToString())); return(Constants.Agent.ReturnCode.TerminatedError); } try { trace.Info($"Version: {Constants.Agent.Version}"); trace.Info($"Commit: {BuildConstants.Source.CommitHash}"); trace.Info($"Culture: {CultureInfo.CurrentCulture.Name}"); trace.Info($"UI Culture: {CultureInfo.CurrentUICulture.Name}"); // Validate directory permissions. string agentDirectory = context.GetDirectory(WellKnownDirectory.Root); trace.Info($"Validating directory permissions for: '{agentDirectory}'"); try { IOUtil.ValidateExecutePermission(agentDirectory); } catch (Exception e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.TerminatedError); } // Parse the command line args. var command = new CommandSettings(context, args); trace.Info("Arguments parsed"); // Defer to the Agent class to execute the command. IAgent agent = context.GetService <IAgent>(); using (agent.TokenSource = new CancellationTokenSource()) { try { return(await agent.ExecuteCommand(command)); } catch (OperationCanceledException) when(agent.TokenSource.IsCancellationRequested) { trace.Info("Agent execution been cancelled."); return(Constants.Agent.ReturnCode.Success); } catch (NonRetryableException e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.TerminatedError); } } } catch (Exception e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.RetryableError); } }
public void CanEnsureConfigure() { using (TestHostContext tc = CreateTestContext()) { Tracing trace = tc.GetTrace(); trace.Info("Creating config manager"); IConfigurationManager configManager = new ConfigurationManager(); configManager.Initialize(tc); trace.Info("Preparing command line arguments"); var command = new CommandSettings( tc, new[] { "configure", "--url", _expectedServerUrl, "--agent", _expectedAgentName, "--pool", _expectedPoolName, "--work", _expectedWorkFolder, "--auth", _expectedAuthType, "--token", _expectedToken }); trace.Info("Constructed."); var expectedPools = new List<TaskAgentPool>() { new TaskAgentPool(_expectedPoolName) { Id = 1 } }; _agentServer.Setup(x => x.GetAgentPoolsAsync(It.IsAny<string>())).Returns(Task.FromResult(expectedPools)); var expectedAgents = new List<TaskAgent>(); _agentServer.Setup(x => x.GetAgentsAsync(It.IsAny<int>(), It.IsAny<string>())).Returns(Task.FromResult(expectedAgents)); var expectedAgent = new TaskAgent(_expectedAgentName) { Id = 1 }; _agentServer.Setup(x => x.AddAgentAsync(It.IsAny<int>(), It.IsAny<TaskAgent>())).Returns(Task.FromResult(expectedAgent)); _agentServer.Setup(x => x.UpdateAgentAsync(It.IsAny<int>(), It.IsAny<TaskAgent>())).Returns(Task.FromResult(expectedAgent)); trace.Info("Ensuring all the required parameters are available in the command line parameter"); configManager.ConfigureAsync(command); _store.Setup(x => x.IsConfigured()).Returns(true); trace.Info("Configured, verifying all the parameter value"); var s = configManager.LoadSettings(); Assert.True(s.ServerUrl.Equals(_expectedServerUrl)); Assert.True(s.AgentName.Equals(_expectedAgentName)); Assert.True(s.PoolName.Equals(_expectedPoolName)); Assert.True(s.WorkFolder.Equals(_expectedWorkFolder)); } }
// Return code definition: (this will be used by service host to determine whether it will re-launch agent.listener) // 0: Agent exit // 1: Terminate failure // 2: Retriable failure // 3: Exit for self update public async static Task<int> MainAsync(string[] args) { using (HostContext context = new HostContext("Agent")) { s_trace = context.GetTrace("AgentProcess"); s_trace.Info($"Agent is built for {Constants.Agent.Platform} - {BuildConstants.AgentPackage.PackageName}."); s_trace.Info($"RuntimeInformation: {RuntimeInformation.OSDescription}."); // Validate the binaries intended for one OS are not running on a different OS. switch (Constants.Agent.Platform) { case Constants.OSPlatform.Linux: if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { Console.WriteLine(StringUtil.Loc("NotLinux")); return Constants.Agent.ReturnCode.TerminatedError; } break; case Constants.OSPlatform.OSX: if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { Console.WriteLine(StringUtil.Loc("NotOSX")); return Constants.Agent.ReturnCode.TerminatedError; } break; case Constants.OSPlatform.Windows: if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { Console.WriteLine(StringUtil.Loc("NotWindows")); return Constants.Agent.ReturnCode.TerminatedError; } break; default: Console.WriteLine(StringUtil.Loc("PlatformNotSupport", RuntimeInformation.OSDescription, Constants.Agent.Platform.ToString())); return Constants.Agent.ReturnCode.TerminatedError; } int rc = Constants.Agent.ReturnCode.Success; try { s_trace.Info($"Version: {Constants.Agent.Version}"); s_trace.Info($"Commit: {BuildConstants.Source.CommitHash}"); s_trace.Info($"Culture: {CultureInfo.CurrentCulture.Name}"); s_trace.Info($"UI Culture: {CultureInfo.CurrentUICulture.Name}"); // // TODO (bryanmac): Need VsoAgent.exe compat shim for SCM // That shim will also provide a compat arg parse // and translate / to -- etc... // // Parse the command line args. var command = new CommandSettings(context, args); s_trace.Info("Arguments parsed"); // Defer to the Agent class to execute the command. IAgent agent = context.GetService<IAgent>(); using (agent.TokenSource = new CancellationTokenSource()) { try { rc = await agent.ExecuteCommand(command); } catch (OperationCanceledException) when (agent.TokenSource.IsCancellationRequested) { s_trace.Info("Agent execution been cancelled."); } } } catch (Exception e) { Console.Error.WriteLine(StringUtil.Format("An error occured. {0}", e.Message)); s_trace.Error(e); rc = Constants.Agent.ReturnCode.RetryableError; } return rc; } }
public void PromptsForWindowsLogonAccount() { using (TestHostContext hc = CreateTestContext()) { // Arrange. var command = new CommandSettings(hc, args: new string[0]); _promptManager .Setup(x => x.ReadValue( Constants.Agent.CommandLine.Args.WindowsLogonAccount, // argName StringUtil.Loc("WindowsLogonAccountNameDescription"), // description false, // secret "some default account", // defaultValue Validators.NTAccountValidator, // validator false)) // unattended .Returns("some windows logon account"); // Act. string actual = command.GetWindowsLogonAccount("some default account"); // Assert. Assert.Equal("some windows logon account", actual); } }
public void GetsFlagUnattended() { using (TestHostContext hc = CreateTestContext()) { // Arrange. var command = new CommandSettings(hc, args: new string[] { "--unattended" }); // Act. bool actual = command.Unattended; // Assert. Assert.True(actual); } }
public void GetsFlagAcceptTeeEula() { using (TestHostContext hc = CreateTestContext()) { // Arrange. var command = new CommandSettings(hc, args: new string[] { "--acceptteeeula" }); // Act. bool actual = command.GetAcceptTeeEula(); // Assert. Assert.True(actual); } }
public void PromptsForWork() { using (TestHostContext hc = CreateTestContext()) { // Arrange. var command = new CommandSettings(hc, args: new string[0]); _promptManager .Setup(x => x.ReadValue( Constants.Agent.CommandLine.Args.Work, // argName StringUtil.Loc("WorkFolderDescription"), // description false, // secret "_work", // defaultValue Validators.NonEmptyValidator, // validator false)) // unattended .Returns("some work"); // Act. string actual = command.GetWork(); // Assert. Assert.Equal("some work", actual); } }
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>(); // Defaults to PAT authentication. string defaultAuthType = Constants.Configuration.PAT; 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(Pipelines.AgentJobRequestMessageUtil.Convert(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 void PassesUnattendedToReadBool() { using (TestHostContext hc = CreateTestContext()) { // Arrange. var command = new CommandSettings(hc, args: new string[] { "--unattended" }); _promptManager .Setup(x => x.ReadBool( Constants.Agent.CommandLine.Flags.AcceptTeeEula, // argName StringUtil.Loc("AcceptTeeEula"), // description false, // defaultValue true)) // unattended .Returns(true); // Act. bool actual = command.GetAcceptTeeEula(); // Assert. Assert.True(actual); } }
public async Task <int> ExecuteCommand(CommandSettings command) { try { _inConfigStage = true; _term.CancelKeyPress += CtrlCHandler; // TODO Unit test to cover this logic Trace.Info(nameof(ExecuteCommand)); var configManager = HostContext.GetService <IConfigurationManager>(); // command is not required, if no command it just starts and/or configures if not configured // TODO: Invalid config prints usage if (command.Help) { PrintUsage(); return(Constants.Agent.ReturnCode.Success); } if (command.Version) { _term.WriteLine(Constants.Agent.Version); return(Constants.Agent.ReturnCode.Success); } if (command.Commit) { _term.WriteLine(BuildConstants.Source.CommitHash); return(Constants.Agent.ReturnCode.Success); } // Unconfigure, remove config files, service and exit if (command.Unconfigure) { try { await configManager.UnconfigureAsync(command); return(Constants.Agent.ReturnCode.Success); } catch (Exception ex) { Trace.Error(ex); _term.WriteError(ex.Message); return(Constants.Agent.ReturnCode.TerminatedError); } } if (command.Run && !configManager.IsConfigured()) { _term.WriteError(StringUtil.Loc("AgentIsNotConfigured")); PrintUsage(); return(Constants.Agent.ReturnCode.TerminatedError); } // unattend mode will not prompt for args if not supplied. Instead will error. if (command.Configure) { try { await configManager.ConfigureAsync(command); return(Constants.Agent.ReturnCode.Success); } catch (Exception ex) { Trace.Error(ex); _term.WriteError(ex.Message); return(Constants.Agent.ReturnCode.TerminatedError); } } Trace.Info("Done evaluating commands"); await configManager.EnsureConfiguredAsync(command); _inConfigStage = false; if (command.NoStart) { Trace.Info("No start."); return(Constants.Agent.ReturnCode.Success); } AgentSettings settings = configManager.LoadSettings(); bool runAsService = configManager.IsServiceConfigured(); if (command.Run || !runAsService) { // Run the agent interactively Trace.Verbose($"Run as service: '{runAsService}'"); return(await RunAsync(TokenSource.Token, settings, runAsService)); } if (runAsService) { // This is helpful if the user tries to start the agent.listener which is already configured or running as service // However user can execute the agent by calling the run command // TODO: Should we check if the service is running and prompt user to start the service if its not already running? _term.WriteLine(StringUtil.Loc("ConfiguredAsRunAsService")); } return(Constants.Agent.ReturnCode.Success); } finally { _term.CancelKeyPress -= CtrlCHandler; } }
// Return code definition: (this will be used by service host to determine whether it will re-launch agent.listener) // 0: Agent exit // 1: Terminate failure // 2: Retriable failure // 3: Exit for self update public async static Task <int> MainAsync(IHostContext context, string[] args) { Tracing trace = context.GetTrace("AgentProcess"); trace.Info($"Agent is built for {Constants.Agent.Platform} - {BuildConstants.AgentPackage.PackageName}."); trace.Info($"RuntimeInformation: {RuntimeInformation.OSDescription}."); var terminal = context.GetService <ITerminal>(); // Validate the binaries intended for one OS are not running on a different OS. switch (Constants.Agent.Platform) { case Constants.OSPlatform.Linux: if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { terminal.WriteLine(StringUtil.Loc("NotLinux")); return(Constants.Agent.ReturnCode.TerminatedError); } break; case Constants.OSPlatform.OSX: if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { terminal.WriteLine(StringUtil.Loc("NotOSX")); return(Constants.Agent.ReturnCode.TerminatedError); } break; case Constants.OSPlatform.Windows: if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { terminal.WriteLine(StringUtil.Loc("NotWindows")); return(Constants.Agent.ReturnCode.TerminatedError); } break; default: terminal.WriteLine(StringUtil.Loc("PlatformNotSupport", RuntimeInformation.OSDescription, Constants.Agent.Platform.ToString())); return(Constants.Agent.ReturnCode.TerminatedError); } try { trace.Info($"Version: {Constants.Agent.Version}"); trace.Info($"Commit: {BuildConstants.Source.CommitHash}"); trace.Info($"Culture: {CultureInfo.CurrentCulture.Name}"); trace.Info($"UI Culture: {CultureInfo.CurrentUICulture.Name}"); // Validate directory permissions. string agentDirectory = context.GetDirectory(WellKnownDirectory.Root); trace.Info($"Validating directory permissions for: '{agentDirectory}'"); try { IOUtil.ValidateExecutePermission(agentDirectory); } catch (Exception e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.TerminatedError); } #if OS_WINDOWS // Validate PowerShell 3.0 or higher is installed. var powerShellExeUtil = context.GetService <IPowerShellExeUtil>(); try { powerShellExeUtil.GetPath(); } catch (Exception e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.TerminatedError); } // Validate .NET Framework 4.5 or higher is installed. var netFrameworkUtil = context.GetService <INetFrameworkUtil>(); if (!netFrameworkUtil.Test(new Version(4, 5))) { terminal.WriteError(StringUtil.Loc("MinimumNetFramework")); return(Constants.Agent.ReturnCode.TerminatedError); } #endif // Parse the command line args. var command = new CommandSettings(context, args); trace.Info("Arguments parsed"); // Up front validation, warn for unrecognized commandline args. var unknownCommandlines = command.Validate(); if (unknownCommandlines.Count > 0) { terminal.WriteError(StringUtil.Loc("UnrecognizedCmdArgs", string.Join(", ", unknownCommandlines))); } // Defer to the Agent class to execute the command. IAgent agent = context.GetService <IAgent>(); try { return(await agent.ExecuteCommand(command)); } catch (OperationCanceledException) when(context.AgentShutdownToken.IsCancellationRequested) { trace.Info("Agent execution been cancelled."); return(Constants.Agent.ReturnCode.Success); } catch (NonRetryableException e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.TerminatedError); } } catch (Exception e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.RetryableError); } }
public void PassesUnattendedToReadValue() { using (TestHostContext hc = CreateTestContext()) { // Arrange. var command = new CommandSettings(hc, args: new string[] { "--unattended" }); _promptManager .Setup(x => x.ReadValue( Constants.Agent.CommandLine.Args.Agent, // argName StringUtil.Loc("AgentName"), // description false, // secret Environment.MachineName, // defaultValue Validators.NonEmptyValidator, // validator true)) // unattended .Returns("some agent"); // Act. string actual = command.GetAgent(); // Assert. Assert.Equal("some agent", actual); } }
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 void PromptsForAuth() { using (TestHostContext hc = CreateTestContext()) { // Arrange. var command = new CommandSettings(hc, args: new string[0]); _promptManager .Setup(x => x.ReadValue( Constants.Agent.CommandLine.Args.Auth, // argName StringUtil.Loc("AuthenticationType"), // description false, // secret "some default auth", // defaultValue Validators.AuthSchemeValidator, // validator false)) // unattended .Returns("some auth"); // Act. string actual = command.GetAuth("some default auth"); // Assert. Assert.Equal("some auth", actual); } }
public async Task<int> ExecuteCommand(CommandSettings command) { try { var agentWebProxy = HostContext.GetService<IVstsAgentWebProxy>(); var agentCertManager = HostContext.GetService<IAgentCertificateManager>(); VssUtil.InitializeVssClientSettings(HostContext.UserAgent, agentWebProxy.WebProxy, agentCertManager.VssClientCertificateManager); _inConfigStage = true; _completedCommand.Reset(); _term.CancelKeyPress += CtrlCHandler; //register a SIGTERM handler HostContext.Unloading += Agent_Unloading; // TODO Unit test to cover this logic Trace.Info(nameof(ExecuteCommand)); var configManager = HostContext.GetService<IConfigurationManager>(); // command is not required, if no command it just starts if configured // TODO: Invalid config prints usage if (command.IsHelp()) { PrintUsage(command); return Constants.Agent.ReturnCode.Success; } if (command.IsVersion()) { _term.WriteLine(BuildConstants.AgentPackage.Version); return Constants.Agent.ReturnCode.Success; } if (command.IsCommit()) { _term.WriteLine(BuildConstants.Source.CommitHash); return Constants.Agent.ReturnCode.Success; } if (command.IsDiagnostics()) { PrintBanner(); _term.WriteLine("Running Diagnostics Only..."); _term.WriteLine(string.Empty); DiagnosticTests diagnostics = new DiagnosticTests(_term); diagnostics.Execute(); return Constants.Agent.ReturnCode.Success; } // Configure agent prompt for args if not supplied // Unattend configure mode will not prompt for args if not supplied and error on any missing or invalid value. if (command.IsConfigureCommand()) { PrintBanner(); try { await configManager.ConfigureAsync(command); return Constants.Agent.ReturnCode.Success; } catch (Exception ex) { Trace.Error(ex); _term.WriteError(ex.Message); return Constants.Agent.ReturnCode.TerminatedError; } } // remove config files, remove service, and exit if (command.IsRemoveCommand()) { try { await configManager.UnconfigureAsync(command); return Constants.Agent.ReturnCode.Success; } catch (Exception ex) { Trace.Error(ex); _term.WriteError(ex.Message); return Constants.Agent.ReturnCode.TerminatedError; } } _inConfigStage = false; // warmup agent process (JIT/CLR) // In scenarios where the agent is single use (used and then thrown away), the system provisioning the agent can call `agent.listener --warmup` before the machine is made available to the pool for use. // this will optimizes the agent process startup time. if (command.IsWarmupCommand()) { var binDir = HostContext.GetDirectory(WellKnownDirectory.Bin); foreach (var assemblyFile in Directory.EnumerateFiles(binDir, "*.dll")) { try { Trace.Info($"Load assembly: {assemblyFile}."); var assembly = Assembly.LoadFrom(assemblyFile); var types = assembly.GetTypes(); foreach (Type loadedType in types) { try { Trace.Info($"Load methods: {loadedType.FullName}."); var methods = loadedType.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); foreach (var method in methods) { if (!method.IsAbstract && !method.ContainsGenericParameters) { Trace.Verbose($"Prepare method: {method.Name}."); RuntimeHelpers.PrepareMethod(method.MethodHandle); } } } catch (Exception ex) { Trace.Error(ex); } } } catch (Exception ex) { Trace.Error(ex); } } return Constants.Agent.ReturnCode.Success; } AgentSettings settings = configManager.LoadSettings(); var store = HostContext.GetService<IConfigurationStore>(); bool configuredAsService = store.IsServiceConfigured(); // Run agent //if (command.Run) // this line is current break machine provisioner. //{ // Error if agent not configured. if (!configManager.IsConfigured()) { _term.WriteError(StringUtil.Loc("AgentIsNotConfigured")); PrintUsage(command); return Constants.Agent.ReturnCode.TerminatedError; } Trace.Verbose($"Configured as service: '{configuredAsService}'"); //Get the startup type of the agent i.e., autostartup, service, manual StartupType startType; var startupTypeAsString = command.GetStartupType(); if (string.IsNullOrEmpty(startupTypeAsString) && configuredAsService) { // We need try our best to make the startup type accurate // The problem is coming from agent autoupgrade, which result an old version service host binary but a newer version agent binary // At that time the servicehost won't pass --startuptype to agent.listener while the agent is actually running as service. // We will guess the startup type only when the agent is configured as service and the guess will based on whether STDOUT/STDERR/STDIN been redirect or not Trace.Info($"Try determine agent startup type base on console redirects."); startType = (Console.IsErrorRedirected && Console.IsInputRedirected && Console.IsOutputRedirected) ? StartupType.Service : StartupType.Manual; } else { if (!Enum.TryParse(startupTypeAsString, true, out startType)) { Trace.Info($"Could not parse the argument value '{startupTypeAsString}' for StartupType. Defaulting to {StartupType.Manual}"); startType = StartupType.Manual; } } Trace.Info($"Set agent startup type - {startType}"); HostContext.StartupType = startType; if (PlatformUtil.RunningOnWindows) { if (store.IsAutoLogonConfigured()) { if (HostContext.StartupType != StartupType.Service) { Trace.Info($"Autologon is configured on the machine, dumping all the autologon related registry settings"); var autoLogonRegManager = HostContext.GetService<IAutoLogonRegistryManager>(); autoLogonRegManager.DumpAutoLogonRegistrySettings(); } else { Trace.Info($"Autologon is configured on the machine but current Agent.Listner.exe is launched from the windows service"); } } } // Run the agent interactively or as service return await RunAsync(settings, command.GetRunOnce()); } finally { _term.CancelKeyPress -= CtrlCHandler; HostContext.Unloading -= Agent_Unloading; _completedCommand.Set(); } }
public void PromptsForRunAsService() { using (TestHostContext hc = CreateTestContext()) { // Arrange. var command = new CommandSettings(hc, args: new string[0]); _promptManager .Setup(x => x.ReadBool( Constants.Agent.CommandLine.Flags.RunAsService, // argName StringUtil.Loc("RunAgentAsServiceDescription"), // description false, // defaultValue false)) // unattended .Returns(true); // Act. bool actual = command.GetRunAsService(); // Assert. Assert.True(actual); } }
public async Task<int> ExecuteCommand(CommandSettings command) { try { WebProxy.ApplyProxySettings(); _inConfigStage = true; _completedCommand.Reset(); _term.CancelKeyPress += CtrlCHandler; //register a SIGTERM handler HostContext.Unloading += Agent_Unloading; // TODO Unit test to cover this logic Trace.Info(nameof(ExecuteCommand)); var configManager = HostContext.GetService<IConfigurationManager>(); // command is not required, if no command it just starts and/or configures if not configured // TODO: Invalid config prints usage if (command.Help) { PrintUsage(); return Constants.Agent.ReturnCode.Success; } if (command.Version) { _term.WriteLine(Constants.Agent.Version); return Constants.Agent.ReturnCode.Success; } if (command.Commit) { _term.WriteLine(BuildConstants.Source.CommitHash); return Constants.Agent.ReturnCode.Success; } // Unconfigure, remove config files, service and exit if (command.Unconfigure) { try { await configManager.UnconfigureAsync(command); return Constants.Agent.ReturnCode.Success; } catch (Exception ex) { Trace.Error(ex); _term.WriteError(ex.Message); return Constants.Agent.ReturnCode.TerminatedError; } } if (command.Run && !configManager.IsConfigured()) { _term.WriteError(StringUtil.Loc("AgentIsNotConfigured")); PrintUsage(); return Constants.Agent.ReturnCode.TerminatedError; } // unattend mode will not prompt for args if not supplied. Instead will error. if (command.Configure) { try { await configManager.ConfigureAsync(command); return Constants.Agent.ReturnCode.Success; } catch (Exception ex) { Trace.Error(ex); _term.WriteError(ex.Message); return Constants.Agent.ReturnCode.TerminatedError; } } Trace.Info("Done evaluating commands"); await configManager.EnsureConfiguredAsync(command); _inConfigStage = false; if (command.NoStart) { Trace.Info("No start."); return Constants.Agent.ReturnCode.Success; } AgentSettings settings = configManager.LoadSettings(); bool runAsService = configManager.IsServiceConfigured(); if (command.Run || !runAsService) { // Run the agent interactively Trace.Verbose($"Run as service: '{runAsService}'"); return await RunAsync(TokenSource.Token, settings, runAsService); } if (runAsService) { // This is helpful if the user tries to start the agent.listener which is already configured or running as service // However user can execute the agent by calling the run command // TODO: Should we check if the service is running and prompt user to start the service if its not already running? _term.WriteLine(StringUtil.Loc("ConfiguredAsRunAsService")); } return Constants.Agent.ReturnCode.Success; } finally { _term.CancelKeyPress -= CtrlCHandler; HostContext.Unloading -= Agent_Unloading; _completedCommand.Set(); } }
public void PromptsForToken() { using (TestHostContext hc = CreateTestContext()) { // Arrange. var command = new CommandSettings(hc, args: new string[0]); _promptManager .Setup(x => x.ReadValue( Constants.Agent.CommandLine.Args.Token, // argName StringUtil.Loc("PersonalAccessToken"), // description true, // secret string.Empty, // defaultValue Validators.NonEmptyValidator, // validator false)) // unattended .Returns("some token"); // Act. string actual = command.GetToken(); // Assert. Assert.Equal("some token", actual); } }
public override void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl) { }
public async void TestExecuteCommandForRunAsService(string[] args, bool configureAsService, Times expectedTimes) { using (var hc = new TestHostContext(this)) { hc.SetSingleton<IConfigurationManager>(_configurationManager.Object); hc.SetSingleton<IPromptManager>(_promptManager.Object); hc.SetSingleton<IMessageListener>(_messageListener.Object); var command = new CommandSettings(hc, args); _configurationManager.Setup(x => x.IsConfigured()).Returns(true); _configurationManager.Setup(x => x.LoadSettings()) .Returns(new AgentSettings { }); _configurationManager.Setup(x => x.IsServiceConfigured()).Returns(configureAsService); _messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>())) .Returns(Task.FromResult(false)); var agent = new Agent.Listener.Agent(); agent.Initialize(hc); agent.TokenSource = new CancellationTokenSource(); await agent.ExecuteCommand(command); _messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), expectedTimes); } }