public async Task<Dictionary<string, string>> GetCapabilitiesAsync(AgentSettings settings, CancellationToken cancellationToken) { Trace.Entering(); ArgUtil.NotNull(settings, nameof(settings)); // Get the providers. var extensionManager = HostContext.GetService<IExtensionManager>(); IEnumerable<ICapabilitiesProvider> providers = extensionManager .GetExtensions<ICapabilitiesProvider>() ?.OrderBy(x => x.Order); // Initialize a dictionary of capabilities. var capabilities = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); // Add each capability returned from each provider. foreach (ICapabilitiesProvider provider in providers ?? new ICapabilitiesProvider[0]) { foreach (Capability capability in await provider.GetCapabilitiesAsync(settings, cancellationToken) ?? new List<Capability>()) { capabilities[capability.Name] = capability.Value; } } return capabilities; }
public override void GenerateScripts(AgentSettings settings) { Trace.Entering(); CalculateServiceName(settings, _svcNamePattern, _svcDisplayPattern); try { string svcShPath = Path.Combine(IOUtil.GetRootPath(), _svcShName); // TODO: encoding? // TODO: Loc strings formatted into MSG_xxx vars in shellscript string svcShContent = File.ReadAllText(Path.Combine(IOUtil.GetBinPath(), _shTemplate)); var tokensToReplace = new Dictionary<string, string> { { "{{SvcDescription}}", ServiceDisplayName }, { "{{SvcNameVar}}", ServiceName } }; svcShContent = tokensToReplace.Aggregate( svcShContent, (current, item) => current.Replace(item.Key, item.Value)); //TODO: encoding? File.WriteAllText(svcShPath, svcShContent); var unixUtil = HostContext.CreateService<IUnixUtil>(); unixUtil.ChmodAsync("755", svcShPath).GetAwaiter().GetResult(); } catch (Exception e) { Trace.Error(e); throw; } }
public int Order => 99; // Process last to override prior. public Task<List<Capability>> GetCapabilitiesAsync(AgentSettings settings, CancellationToken cancellationToken) { ArgUtil.NotNull(settings, nameof(settings)); var capabilities = new List<Capability>(); Add(capabilities, "Agent.Name", settings.AgentName ?? string.Empty); switch (Constants.Agent.Platform) { case Constants.OSPlatform.Linux: Add(capabilities, "Agent.OS", "linux"); break; case Constants.OSPlatform.OSX: Add(capabilities, "Agent.OS", "darwin"); break; case Constants.OSPlatform.Windows: Add(capabilities, "Agent.OS", Environment.GetEnvironmentVariable("OS")); break; } #if OS_WINDOWS Add(capabilities, "Agent.OSVersion", GetOSVersionString()); Add(capabilities, "Cmd", Environment.GetEnvironmentVariable("comspec")); #endif Add(capabilities, "Agent.Version", Constants.Agent.Version); Add(capabilities, "Agent.ComputerName", Environment.MachineName ?? string.Empty); return Task.FromResult(capabilities); }
public async Task<List<Capability>> GetCapabilitiesAsync(AgentSettings settings, CancellationToken cancellationToken) { Trace.Entering(); // Check the cache. if (_capabilities != null) { Trace.Info("Found in cached."); return _capabilities; } // Build the list of capabilities. var builder = new CapabilitiesBuilder(HostContext, cancellationToken); builder.Check( name: "AndroidSDK", fileName: "android", filePaths: new[] { Path.Combine(Environment.GetEnvironmentVariable("ANDROID_STUDIO") ?? string.Empty, "tools/android"), Path.Combine(Environment.GetEnvironmentVariable("HOME") ?? string.Empty, "Library/Developer/Xamarin/android-sdk-macosx/tools/android"), }); builder.Check(name: "ant"); builder.Check(name: "bundler", fileName: "bundle"); builder.Check(name: "clang"); builder.Check(name: "cmake"); builder.Check(name: "curl"); builder.Check(name: "git"); builder.Check(name: "gulp"); builder.Check(name: "java"); builder.Check(name: "JDK", fileName: "javac"); builder.Check(name: "make"); builder.Check(name: "maven", fileName: "mvn"); builder.Check(name: "MSBuild", fileName: "xbuild"); builder.Check(name: "node.js", fileName: "node"); builder.Check(name: "node.js", fileName: "nodejs"); builder.Check(name: "npm"); builder.Check(name: "python"); builder.Check(name: "python3"); builder.Check(name: "sh"); builder.Check(name: "subversion", fileName: "svn"); builder.Check(name: "ruby"); builder.Check(name: "rake"); builder.Check( name: "Xamarin.iOS", fileName: "mdtool", filePaths: new string[] { "/Applications/Xamarin Studio.app/Contents/MacOS/mdtool" }); builder.Check( name: "Xamarin.Android", fileName: "mandroid", filePaths: new string[] { "/Library/Frameworks/Xamarin.Android.framework/Commands/mandroid" }); await builder.CheckToolOutputAsync( name: "xcode", fileName: "xcode-select", arguments: "-p"); // Cache and return the values. _capabilities = builder.ToList(); return _capabilities; }
public override bool ConfigureService( AgentSettings settings, CommandSettings command) { Trace.Entering(); throw new NotSupportedException("Systemd Configure Service"); }
public MessageListenerL0() { _settings = new AgentSettings { AgentId = 1, AgentName = "myagent", PoolId = 123, PoolName = "default", ServerUrl = "http://myserver", WorkFolder = "_work" }; _config = new Mock<IConfigurationManager>(); _config.Setup(x => x.LoadSettings()).Returns(_settings); _agentServer = new Mock<IAgentServer>(); _credMgr = new Mock<ICredentialManager>(); _capabilitiesManager = new Mock<ICapabilitiesManager>(); }
public int Order => 1; // Process first so other providers can override. public Task<List<Capability>> GetCapabilitiesAsync(AgentSettings settings, CancellationToken cancellationToken) { Trace.Entering(); var capabilities = new List<Capability>(); // Initialize the ignored hash set. #if OS_WINDOWS var comparer = StringComparer.OrdinalIgnoreCase; #else var comparer = StringComparer.Ordinal; #endif var ignored = new HashSet<string>(s_wellKnownIgnored, comparer); // Also ignore env vars specified by the 'VSO_AGENT_IGNORE' env var. IDictionary variables = Environment.GetEnvironmentVariables(); if (variables.Contains(CustomIgnore)) { IEnumerable<string> additionalIgnored = (variables[CustomIgnore] as string ?? string.Empty) .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim()) .Where(x => !string.IsNullOrEmpty(x)); foreach (string ignore in additionalIgnored) { Trace.Info($"Ignore: '{ignore}'"); ignored.Add(ignore); // Handles duplicates gracefully. } } // Get filtered env vars. IEnumerable<string> names = variables.Keys .Cast<string>() .Where(x => !string.IsNullOrEmpty(x)) .OrderBy(x => x.ToUpperInvariant()); foreach (string name in names) { string value = variables[name] as string ?? string.Empty; if (ignored.Contains(name) || value.Length >= IgnoreValueLength) { Trace.Info($"Skipping: '{name}'"); continue; } Trace.Info($"Adding '{name}': '{value}'"); capabilities.Add(new Capability(name, value)); } return Task.FromResult(capabilities); }
public void ProcessParameterDeserializer_ConvertsExportedAgentSettings_TFS() { var agentSettings = new AgentSettings { TagComparison = TagComparison.MatchExactly, MaxExecutionTime = TimeSpan.MaxValue, MaxWaitTime = TimeSpan.MaxValue, Name = "test", Tags = new StringList("tagA,tagB") }; var expBuildDef = this.Serialize(new { TfvcAgentSettings = (AgentSettingsBuildParameter)agentSettings }); var procParam = new[] { "AgentSettings", expBuildDef }; ExportedProcessParameterTransformer.ProcessParameterDeserializer(procParam).ShouldBeEquivalentTo(agentSettings); }
protected void CalculateServiceName(AgentSettings settings, string serviceNamePattern, string serviceDisplayNamePattern) { Trace.Info(nameof(this.CalculateServiceName)); var accountName = new Uri(settings.ServerUrl).Host.Split('.').FirstOrDefault(); if (string.IsNullOrEmpty(accountName)) { // TODO: Localize this error message: throw new InvalidOperationException("CannotFindHostName"); } ServiceName = StringUtil.Format(serviceNamePattern, accountName, settings.AgentName); ServiceDisplayName = StringUtil.Format(serviceDisplayNamePattern, accountName, settings.AgentName); }
public async void TestGetCapabilities() { using (var hc = new TestHostContext(this)) using (var tokenSource = new CancellationTokenSource()) { // Arrange var provider = new AgentCapabilitiesProvider(); provider.Initialize(hc); var settings = new AgentSettings() { AgentName = "IAmAgent007" }; // Act List<Capability> capabilities = await provider.GetCapabilitiesAsync(settings, tokenSource.Token); // Assert Assert.NotNull(capabilities); Capability agentNameCapability = capabilities.SingleOrDefault(x => string.Equals(x.Name, "Agent.Name", StringComparison.Ordinal)); Assert.NotNull(agentNameCapability); Assert.Equal("IAmAgent007", agentNameCapability.Value); } }
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); } }
public async Task<List<Capability>> GetCapabilitiesAsync(AgentSettings settings, CancellationToken cancellationToken) { Trace.Entering(); var capabilities = new List<Capability>(); string powerShellExe = HostContext.GetService<IPowerShellExeUtil>().GetPath(); string scriptFile = Path.Combine(IOUtil.GetBinPath(), "powershell", "Add-Capabilities.ps1").Replace("'", "''"); ArgUtil.File(scriptFile, nameof(scriptFile)); string arguments = $@"-NoLogo -Sta -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command "". '{scriptFile}'"""; using (var processInvoker = HostContext.CreateService<IProcessInvoker>()) { processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs args) => { Trace.Info($"STDOUT: {args.Data}"); Capability capability; if (TryParseCapability(args.Data, out capability)) { Trace.Info($"Adding '{capability.Name}': '{capability.Value}'"); capabilities.Add(capability); } }; processInvoker.ErrorDataReceived += (object sender, ProcessDataReceivedEventArgs args) => { Trace.Info($"STDERR: {args.Data}"); }; await processInvoker.ExecuteAsync( workingDirectory: Path.GetDirectoryName(scriptFile), fileName: powerShellExe, arguments: arguments, environment: null, cancellationToken: cancellationToken); } return capabilities; }
public async Task<Boolean> CreateSessionAsync(CancellationToken token) { Trace.Entering(); // Settings var configManager = HostContext.GetService<IConfigurationManager>(); _settings = configManager.LoadSettings(); int agentPoolId = _settings.PoolId; var serverUrl = _settings.ServerUrl; Trace.Info(_settings); // Capabilities. // TODO: LOC _term.WriteLine("Scanning for tool capabilities."); Dictionary<string, string> systemCapabilities = await HostContext.GetService<ICapabilitiesManager>().GetCapabilitiesAsync(_settings, token); // Create connection. Trace.Verbose("Loading Credentials"); var credMgr = HostContext.GetService<ICredentialManager>(); VssCredentials creds = credMgr.LoadCredentials(); Uri uri = new Uri(serverUrl); VssConnection conn = ApiUtil.CreateConnection(uri, creds); var agent = new TaskAgentReference { Id = _settings.AgentId, Name = _settings.AgentName, Version = Constants.Agent.Version, }; string sessionName = $"{Environment.MachineName ?? "AGENT"}"; var taskAgentSession = new TaskAgentSession(sessionName, agent, systemCapabilities); var agentSvr = HostContext.GetService<IAgentServer>(); string errorMessage = string.Empty; bool encounteringError = false; // TODO: LOC _term.WriteLine("Connecting to the server."); while (true) { token.ThrowIfCancellationRequested(); Trace.Info($"Attempt to create session."); try { Trace.Info("Connecting to the Agent Server..."); await agentSvr.ConnectAsync(conn); Session = await agentSvr.CreateAgentSessionAsync( _settings.PoolId, taskAgentSession, token); Trace.Info($"Session created."); if (encounteringError) { _term.WriteLine(StringUtil.Loc("QueueConnected", DateTime.UtcNow)); _sessionCreationExceptionTracker.Clear(); encounteringError = false; } return true; } catch (OperationCanceledException) when (token.IsCancellationRequested) { Trace.Info("Session creation has been cancelled."); throw; } catch (Exception ex) { Trace.Error("Catch exception during create session."); Trace.Error(ex); if (!IsSessionCreationExceptionRetriable(ex)) { _term.WriteError(StringUtil.Loc("SessionCreateFailed", ex.Message)); return false; } if (!encounteringError) //print the message only on the first error { _term.WriteError(StringUtil.Loc("QueueConError", DateTime.UtcNow, ex.Message, _sessionCreationRetryInterval.TotalSeconds)); encounteringError = true; } Trace.Info("Sleeping for {0} seconds before retrying.", _sessionCreationRetryInterval.TotalSeconds); await HostContext.Delay(_sessionCreationRetryInterval, token); } } }
private TestHostContext CreateTestContext(CancellationTokenSource _tokenSource, [CallerMemberName] String testName = "") { var hc = new TestHostContext(this, testName); _jobEc = new Agent.Worker.ExecutionContext(); _taskManager = new Mock <ITaskManager>(); _jobServerQueue = new Mock <IJobServerQueue>(); _config = new Mock <IConfigurationStore>(); _logger = new Mock <IPagingLogger>(); _proxy = new Mock <IVstsAgentWebProxy>(); _cert = new Mock <IAgentCertificateManager>(); _express = new Mock <IExpressionManager>(); _containerProvider = new Mock <IContainerOperationProvider>(); _logPlugin = new Mock <IAgentLogPlugin>(); TaskRunner step1 = new TaskRunner(); TaskRunner step2 = new TaskRunner(); TaskRunner step3 = new TaskRunner(); TaskRunner step4 = new TaskRunner(); TaskRunner step5 = new TaskRunner(); TaskRunner step6 = new TaskRunner(); TaskRunner step7 = new TaskRunner(); TaskRunner step8 = new TaskRunner(); TaskRunner step9 = new TaskRunner(); TaskRunner step10 = new TaskRunner(); TaskRunner step11 = new TaskRunner(); TaskRunner step12 = new TaskRunner(); _logger.Setup(x => x.Setup(It.IsAny <Guid>(), It.IsAny <Guid>())); var settings = new AgentSettings { AgentId = 1, AgentName = "agent1", ServerUrl = "https://test.visualstudio.com", WorkFolder = "_work", }; _config.Setup(x => x.GetSettings()) .Returns(settings); _proxy.Setup(x => x.ProxyAddress) .Returns(string.Empty); TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference(); TimelineReference timeline = new Timeline(Guid.NewGuid()); JobEnvironment environment = new JobEnvironment(); environment.Variables[Constants.Variables.System.Culture] = "en-US"; environment.SystemConnection = new ServiceEndpoint() { Name = WellKnownServiceEndpointNames.SystemVssConnection, Url = new Uri("https://test.visualstudio.com"), Authorization = new EndpointAuthorization() { Scheme = "Test", } }; environment.SystemConnection.Authorization.Parameters["AccessToken"] = "token"; List <TaskInstance> tasks = new List <TaskInstance>() { new TaskInstance() { InstanceId = Guid.NewGuid(), DisplayName = "task1", }, new TaskInstance() { InstanceId = Guid.NewGuid(), DisplayName = "task2", }, new TaskInstance() { InstanceId = Guid.NewGuid(), DisplayName = "task3", }, new TaskInstance() { InstanceId = Guid.NewGuid(), DisplayName = "task4", }, new TaskInstance() { InstanceId = Guid.NewGuid(), DisplayName = "task5", }, new TaskInstance() { InstanceId = Guid.NewGuid(), DisplayName = "task6", }, new TaskInstance() { InstanceId = Guid.NewGuid(), DisplayName = "task7", }, }; Guid JobId = Guid.NewGuid(); _message = Pipelines.AgentJobRequestMessageUtil.Convert(new AgentJobRequestMessage(plan, timeline, JobId, testName, testName, environment, tasks)); _taskManager.Setup(x => x.DownloadAsync(It.IsAny <IExecutionContext>(), It.IsAny <IEnumerable <Pipelines.TaskStep> >())) .Returns(Task.CompletedTask); _taskManager.Setup(x => x.Load(It.Is <Pipelines.TaskStep>(t => t.DisplayName == "task1"))) .Returns(new Definition() { Data = new DefinitionData() { PreJobExecution = null, Execution = new ExecutionData(), PostJobExecution = null, }, }); _taskManager.Setup(x => x.Load(It.Is <Pipelines.TaskStep>(t => t.DisplayName == "task2"))) .Returns(new Definition() { Data = new DefinitionData() { PreJobExecution = new ExecutionData(), Execution = new ExecutionData(), PostJobExecution = new ExecutionData(), }, }); _taskManager.Setup(x => x.Load(It.Is <Pipelines.TaskStep>(t => t.DisplayName == "task3"))) .Returns(new Definition() { Data = new DefinitionData() { PreJobExecution = new ExecutionData(), Execution = null, PostJobExecution = new ExecutionData(), }, }); _taskManager.Setup(x => x.Load(It.Is <Pipelines.TaskStep>(t => t.DisplayName == "task4"))) .Returns(new Definition() { Data = new DefinitionData() { PreJobExecution = new ExecutionData(), Execution = null, PostJobExecution = null, }, }); _taskManager.Setup(x => x.Load(It.Is <Pipelines.TaskStep>(t => t.DisplayName == "task5"))) .Returns(new Definition() { Data = new DefinitionData() { PreJobExecution = null, Execution = null, PostJobExecution = new ExecutionData(), }, }); _taskManager.Setup(x => x.Load(It.Is <Pipelines.TaskStep>(t => t.DisplayName == "task6"))) .Returns(new Definition() { Data = new DefinitionData() { PreJobExecution = new ExecutionData(), Execution = new ExecutionData(), PostJobExecution = null, }, }); _taskManager.Setup(x => x.Load(It.Is <Pipelines.TaskStep>(t => t.DisplayName == "task7"))) .Returns(new Definition() { Data = new DefinitionData() { PreJobExecution = null, Execution = new ExecutionData(), PostJobExecution = new ExecutionData(), }, }); hc.SetSingleton(_taskManager.Object); hc.SetSingleton(_config.Object); hc.SetSingleton(_jobServerQueue.Object); hc.SetSingleton(_proxy.Object); hc.SetSingleton(_cert.Object); hc.SetSingleton(_express.Object); hc.SetSingleton(_containerProvider.Object); hc.SetSingleton(_logPlugin.Object); hc.EnqueueInstance <IPagingLogger>(_logger.Object); // jobcontext logger hc.EnqueueInstance <IPagingLogger>(_logger.Object); // init step logger hc.EnqueueInstance <IPagingLogger>(_logger.Object); // step 1 hc.EnqueueInstance <IPagingLogger>(_logger.Object); hc.EnqueueInstance <IPagingLogger>(_logger.Object); hc.EnqueueInstance <IPagingLogger>(_logger.Object); hc.EnqueueInstance <IPagingLogger>(_logger.Object); hc.EnqueueInstance <IPagingLogger>(_logger.Object); hc.EnqueueInstance <IPagingLogger>(_logger.Object); hc.EnqueueInstance <IPagingLogger>(_logger.Object); hc.EnqueueInstance <IPagingLogger>(_logger.Object); hc.EnqueueInstance <IPagingLogger>(_logger.Object); hc.EnqueueInstance <IPagingLogger>(_logger.Object); hc.EnqueueInstance <IPagingLogger>(_logger.Object); // step 12 hc.EnqueueInstance <ITaskRunner>(step1); hc.EnqueueInstance <ITaskRunner>(step2); hc.EnqueueInstance <ITaskRunner>(step3); hc.EnqueueInstance <ITaskRunner>(step4); hc.EnqueueInstance <ITaskRunner>(step5); hc.EnqueueInstance <ITaskRunner>(step6); hc.EnqueueInstance <ITaskRunner>(step7); hc.EnqueueInstance <ITaskRunner>(step8); hc.EnqueueInstance <ITaskRunner>(step9); hc.EnqueueInstance <ITaskRunner>(step10); hc.EnqueueInstance <ITaskRunner>(step11); hc.EnqueueInstance <ITaskRunner>(step12); _jobEc.Initialize(hc); _jobEc.InitializeJob(_message, _tokenSource.Token); return(hc); }
public ConfigurationManagerL0() { _agentServer = new Mock <IAgentServer>(); _locationServer = new Mock <ILocationServer>(); _credMgr = new Mock <ICredentialManager>(); _promptManager = new Mock <IPromptManager>(); _store = new Mock <IConfigurationStore>(); _extnMgr = new Mock <IExtensionManager>(); _rsaKeyManager = new Mock <IRSAKeyManager>(); _machineGroupServer = new Mock <IDeploymentGroupServer>(); _vstsAgentWebProxy = new Mock <IVstsAgentWebProxy>(); _cert = new Mock <IAgentCertificateManager>(); #if OS_WINDOWS _serviceControlManager = new Mock <IWindowsServiceControlManager>(); #endif #if !OS_WINDOWS _serviceControlManager = new Mock <ILinuxServiceControlManager>(); #endif _capabilitiesManager = new CapabilitiesManager(); var expectedAgent = new TaskAgent(_expectedAgentName) { Id = 1 }; var expectedDeploymentMachine = new DeploymentMachine() { Agent = expectedAgent, Id = _expectedDeploymentMachineId }; expectedAgent.Authorization = new TaskAgentAuthorization { ClientId = Guid.NewGuid(), AuthorizationUrl = new Uri("http://localhost:8080/tfs"), }; var connectionData = new ConnectionData() { InstanceId = Guid.NewGuid(), DeploymentType = DeploymentFlags.Hosted, DeploymentId = Guid.NewGuid() }; _agentServer.Setup(x => x.ConnectAsync(It.IsAny <VssConnection>())).Returns(Task.FromResult <object>(null)); _locationServer.Setup(x => x.ConnectAsync(It.IsAny <VssConnection>())).Returns(Task.FromResult <object>(null)); _locationServer.Setup(x => x.GetConnectionDataAsync()).Returns(Task.FromResult <ConnectionData>(connectionData)); _machineGroupServer.Setup(x => x.ConnectAsync(It.IsAny <VssConnection>())).Returns(Task.FromResult <object>(null)); _machineGroupServer.Setup(x => x.UpdateDeploymentTargetsAsync(It.IsAny <Guid>(), It.IsAny <int>(), It.IsAny <List <DeploymentMachine> >())); _machineGroupServer.Setup(x => x.AddDeploymentTargetAsync(It.IsAny <Guid>(), It.IsAny <int>(), It.IsAny <DeploymentMachine>())).Returns(Task.FromResult(expectedDeploymentMachine)); _machineGroupServer.Setup(x => x.ReplaceDeploymentTargetAsync(It.IsAny <Guid>(), It.IsAny <int>(), It.IsAny <int>(), It.IsAny <DeploymentMachine>())).Returns(Task.FromResult(expectedDeploymentMachine)); _machineGroupServer.Setup(x => x.GetDeploymentTargetsAsync(It.IsAny <Guid>(), It.IsAny <int>(), It.IsAny <string>())).Returns(Task.FromResult(new List <DeploymentMachine>() { })); _machineGroupServer.Setup(x => x.DeleteDeploymentTargetAsync(It.IsAny <string>(), It.IsAny <int>(), It.IsAny <int>())).Returns(Task.FromResult <object>(null)); _store.Setup(x => x.IsConfigured()).Returns(false); _store.Setup(x => x.HasCredentials()).Returns(false); _store.Setup(x => x.GetSettings()).Returns(() => _configMgrAgentSettings); _store.Setup(x => x.SaveSettings(It.IsAny <AgentSettings>())) .Callback((AgentSettings settings) => { _configMgrAgentSettings = settings; }); _credMgr.Setup(x => x.GetCredentialProvider(It.IsAny <string>())).Returns(new TestAgentCredential()); #if !OS_WINDOWS _serviceControlManager.Setup(x => x.GenerateScripts(It.IsAny <AgentSettings>())); #endif var expectedPools = new List <TaskAgentPool>() { new TaskAgentPool(_expectedPoolName) { Id = _expectedPoolId } }; _agentServer.Setup(x => x.GetAgentPoolsAsync(It.IsAny <string>(), It.IsAny <TaskAgentPoolType>())).Returns(Task.FromResult(expectedPools)); var expectedAgents = new List <TaskAgent>(); _agentServer.Setup(x => x.GetAgentsAsync(It.IsAny <int>(), It.IsAny <string>())).Returns(Task.FromResult(expectedAgents)); _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)); rsa = new RSACryptoServiceProvider(2048); _rsaKeyManager.Setup(x => x.CreateKey()).Returns(rsa); }
public abstract void GenerateScripts(AgentSettings settings);
//create worker manager, create message listener and start listening to the queue private async Task<int> RunAsync(CancellationToken token, AgentSettings settings, bool runAsService) { Trace.Info(nameof(RunAsync)); // Load the settings. _poolId = settings.PoolId; _listener = HostContext.GetService<IMessageListener>(); if (!await _listener.CreateSessionAsync(token)) { return Constants.Agent.ReturnCode.TerminatedError; } _term.WriteLine(StringUtil.Loc("ListenForJobs", DateTime.UtcNow)); IJobDispatcher jobDispatcher = null; CancellationTokenSource messageQueueLoopTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token); try { var notification = HostContext.GetService<IJobNotification>(); notification.StartClient(settings.NotificationPipeName, token); bool disableAutoUpdate = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("agent.disableupdate")); bool autoUpdateInProgress = false; Task<bool> selfUpdateTask = null; jobDispatcher = HostContext.CreateService<IJobDispatcher>(); while (!token.IsCancellationRequested) { TaskAgentMessage message = null; bool skipMessageDeletion = false; try { Task<TaskAgentMessage> getNextMessage = _listener.GetNextMessageAsync(messageQueueLoopTokenSource.Token); if (autoUpdateInProgress) { Trace.Verbose("Auto update task running at backend, waiting for getNextMessage or selfUpdateTask to finish."); Task completeTask = await Task.WhenAny(getNextMessage, selfUpdateTask); if (completeTask == selfUpdateTask) { autoUpdateInProgress = false; if (await selfUpdateTask) { Trace.Info("Auto update task finished at backend, an agent update is ready to apply exit the current agent instance."); Trace.Info("Stop message queue looping."); messageQueueLoopTokenSource.Cancel(); try { await getNextMessage; } catch (Exception ex) { Trace.Info($"Ignore any exception after cancel message loop. {ex}"); } return Constants.Agent.ReturnCode.AgentUpdating; } else { Trace.Info("Auto update task finished at backend, there is no available agent update needs to apply, continue message queue looping."); } } } message = await getNextMessage; //get next message if (string.Equals(message.MessageType, AgentRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase)) { if (disableAutoUpdate) { Trace.Info("Refresh message received, skip autoupdate since environment variable agent.disableupdate is set."); } else { if (autoUpdateInProgress == false) { autoUpdateInProgress = true; var selfUpdater = HostContext.GetService<ISelfUpdater>(); selfUpdateTask = selfUpdater.SelfUpdate(jobDispatcher, !runAsService, token); Trace.Info("Refresh message received, kick-off selfupdate background process."); } else { Trace.Info("Refresh message received, skip autoupdate since a previous autoupdate is already running."); } } } else if (string.Equals(message.MessageType, JobRequestMessage.MessageType, StringComparison.OrdinalIgnoreCase)) { if (autoUpdateInProgress) { skipMessageDeletion = true; } else { var newJobMessage = JsonUtility.FromString<JobRequestMessage>(message.Body); jobDispatcher.Run(newJobMessage); } } else if (string.Equals(message.MessageType, JobCancelMessage.MessageType, StringComparison.OrdinalIgnoreCase)) { var cancelJobMessage = JsonUtility.FromString<JobCancelMessage>(message.Body); bool jobCancelled = jobDispatcher.Cancel(cancelJobMessage); skipMessageDeletion = autoUpdateInProgress && !jobCancelled; } } finally { if (!skipMessageDeletion && message != null) { try { await DeleteMessageAsync(message); } catch (Exception ex) { Trace.Error($"Catch exception during delete message from message queue. message id: {message.MessageId}"); Trace.Error(ex); } finally { message = null; } } } } } finally { if (jobDispatcher != null) { await jobDispatcher.ShutdownAsync(); } //TODO: make sure we don't mask more important exception await _listener.DeleteSessionAsync(); messageQueueLoopTokenSource.Dispose(); } return Constants.Agent.ReturnCode.Success; }
//create worker manager, create message listener and start listening to the queue private async Task <int> RunAsync(AgentSettings settings) { Trace.Info(nameof(RunAsync)); _listener = HostContext.GetService <IMessageListener>(); if (!await _listener.CreateSessionAsync(HostContext.AgentShutdownToken)) { return(Constants.Agent.ReturnCode.TerminatedError); } _term.WriteLine(StringUtil.Loc("ListenForJobs", DateTime.UtcNow)); IJobDispatcher jobDispatcher = null; CancellationTokenSource messageQueueLoopTokenSource = CancellationTokenSource.CreateLinkedTokenSource(HostContext.AgentShutdownToken); try { var notification = HostContext.GetService <IJobNotification>(); if (!String.IsNullOrEmpty(settings.NotificationSocketAddress)) { notification.StartClient(settings.NotificationSocketAddress); } else { notification.StartClient(settings.NotificationPipeName, HostContext.AgentShutdownToken); } // this is not a reliable way to disable auto update. // we need server side work to really enable the feature // https://github.com/Microsoft/vsts-agent/issues/446 (Feature: Allow agent / pool to opt out of automatic updates) bool disableAutoUpdate = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("agent.disableupdate")); bool autoUpdateInProgress = false; Task <bool> selfUpdateTask = null; jobDispatcher = HostContext.CreateService <IJobDispatcher>(); while (!HostContext.AgentShutdownToken.IsCancellationRequested) { TaskAgentMessage message = null; bool skipMessageDeletion = false; try { Task <TaskAgentMessage> getNextMessage = _listener.GetNextMessageAsync(messageQueueLoopTokenSource.Token); if (autoUpdateInProgress) { Trace.Verbose("Auto update task running at backend, waiting for getNextMessage or selfUpdateTask to finish."); Task completeTask = await Task.WhenAny(getNextMessage, selfUpdateTask); if (completeTask == selfUpdateTask) { autoUpdateInProgress = false; if (await selfUpdateTask) { Trace.Info("Auto update task finished at backend, an agent update is ready to apply exit the current agent instance."); Trace.Info("Stop message queue looping."); messageQueueLoopTokenSource.Cancel(); try { await getNextMessage; } catch (Exception ex) { Trace.Info($"Ignore any exception after cancel message loop. {ex}"); } return(Constants.Agent.ReturnCode.AgentUpdating); } else { Trace.Info("Auto update task finished at backend, there is no available agent update needs to apply, continue message queue looping."); } } } message = await getNextMessage; //get next message if (string.Equals(message.MessageType, AgentRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase)) { if (disableAutoUpdate) { Trace.Info("Refresh message received, skip autoupdate since environment variable agent.disableupdate is set."); } else { if (autoUpdateInProgress == false) { autoUpdateInProgress = true; var agentUpdateMessage = JsonUtility.FromString <AgentRefreshMessage>(message.Body); var selfUpdater = HostContext.GetService <ISelfUpdater>(); selfUpdateTask = selfUpdater.SelfUpdate(agentUpdateMessage, jobDispatcher, HostContext.StartupType != StartupType.Service, HostContext.AgentShutdownToken); Trace.Info("Refresh message received, kick-off selfupdate background process."); } else { Trace.Info("Refresh message received, skip autoupdate since a previous autoupdate is already running."); } } } else if (string.Equals(message.MessageType, JobRequestMessageTypes.AgentJobRequest, StringComparison.OrdinalIgnoreCase)) { if (autoUpdateInProgress) { skipMessageDeletion = true; } else { var newJobMessage = JsonUtility.FromString <AgentJobRequestMessage>(message.Body); jobDispatcher.Run(newJobMessage); } } else if (string.Equals(message.MessageType, JobCancelMessage.MessageType, StringComparison.OrdinalIgnoreCase)) { var cancelJobMessage = JsonUtility.FromString <JobCancelMessage>(message.Body); bool jobCancelled = jobDispatcher.Cancel(cancelJobMessage); skipMessageDeletion = autoUpdateInProgress && !jobCancelled; } else { Trace.Error($"Received message {message.MessageId} with unsupported message type {message.MessageType}."); } } finally { if (!skipMessageDeletion && message != null) { try { await _listener.DeleteMessageAsync(message); } catch (Exception ex) { Trace.Error($"Catch exception during delete message from message queue. message id: {message.MessageId}"); Trace.Error(ex); } finally { message = null; } } } } } finally { if (jobDispatcher != null) { await jobDispatcher.ShutdownAsync(); } //TODO: make sure we don't mask more important exception await _listener.DeleteSessionAsync(); messageQueueLoopTokenSource.Dispose(); } return(Constants.Agent.ReturnCode.Success); }
private async Task DownloadAsync(IExecutionContext executionContext, Pipelines.TaskStepDefinitionReference task) { Trace.Entering(); ArgUtil.NotNull(executionContext, nameof(executionContext)); ArgUtil.NotNull(task, nameof(task)); ArgUtil.NotNullOrEmpty(task.Version, nameof(task.Version)); var taskServer = HostContext.GetService <ITaskServer>(); // first check to see if we already have the task string destDirectory = GetDirectory(task); Trace.Info($"Ensuring task exists: ID '{task.Id}', version '{task.Version}', name '{task.Name}', directory '{destDirectory}'."); var configurationStore = HostContext.GetService <IConfigurationStore>(); AgentSettings settings = configurationStore.GetSettings(); Boolean signingEnabled = (settings.SignatureVerification != null && settings.SignatureVerification.Mode != SignatureVerificationMode.None); Boolean alwaysExtractTask = signingEnabled || settings.AlwaysExtractTask; if (File.Exists(destDirectory + ".completed") && !alwaysExtractTask) { executionContext.Debug($"Task '{task.Name}' already downloaded at '{destDirectory}'."); return; } String taskZipPath = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.TaskZips), $"{task.Name}_{task.Id}_{task.Version}.zip"); if (alwaysExtractTask && File.Exists(taskZipPath)) { executionContext.Debug($"Task '{task.Name}' already downloaded at '{taskZipPath}'."); // Extract a new zip every time IOUtil.DeleteDirectory(destDirectory, executionContext.CancellationToken); ExtractZip(taskZipPath, destDirectory); return; } // delete existing task folder. Trace.Verbose("Deleting task destination folder: {0}", destDirectory); IOUtil.DeleteDirectory(destDirectory, CancellationToken.None); // Inform the user that a download is taking place. The download could take a while if // the task zip is large. It would be nice to print the localized name, but it is not // available from the reference included in the job message. executionContext.Output(StringUtil.Loc("DownloadingTask0", task.Name, task.Version)); string zipFile = string.Empty; var version = new TaskVersion(task.Version); //download and extract task in a temp folder and rename it on success string tempDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Tasks), "_temp_" + Guid.NewGuid()); try { Directory.CreateDirectory(tempDirectory); int retryCount = 0; // Allow up to 20 * 60s for any task to be downloaded from service. // Base on Kusto, the longest we have on the service today is over 850 seconds. // Timeout limit can be overwrite by environment variable var timeoutSeconds = AgentKnobs.TaskDownloadTimeout.GetValue(UtilKnobValueContext.Instance()).AsInt(); while (retryCount < 3) { using (var taskDownloadTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds))) using (var taskDownloadCancellation = CancellationTokenSource.CreateLinkedTokenSource(taskDownloadTimeout.Token, executionContext.CancellationToken)) { try { zipFile = Path.Combine(tempDirectory, string.Format("{0}.zip", Guid.NewGuid())); //open zip stream in async mode using (FileStream fs = new FileStream(zipFile, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: _defaultFileStreamBufferSize, useAsync: true)) using (Stream result = await taskServer.GetTaskContentZipAsync(task.Id, version, taskDownloadCancellation.Token)) { await result.CopyToAsync(fs, _defaultCopyBufferSize, taskDownloadCancellation.Token); await fs.FlushAsync(taskDownloadCancellation.Token); // download succeed, break out the retry loop. break; } } catch (OperationCanceledException) when(executionContext.CancellationToken.IsCancellationRequested) { Trace.Info($"Task download has been cancelled."); throw; } catch (Exception ex) when(retryCount < 2) { retryCount++; Trace.Error($"Fail to download task '{task.Id} ({task.Name}/{task.Version})' -- Attempt: {retryCount}"); Trace.Error(ex); if (taskDownloadTimeout.Token.IsCancellationRequested) { // task download didn't finish within timeout executionContext.Warning(StringUtil.Loc("TaskDownloadTimeout", task.Name, timeoutSeconds)); } else { executionContext.Warning(StringUtil.Loc("TaskDownloadFailed", task.Name, ex.Message)); if (ex.InnerException != null) { executionContext.Warning("Inner Exception: {ex.InnerException.Message}"); } } } } if (String.IsNullOrEmpty(Environment.GetEnvironmentVariable("VSTS_TASK_DOWNLOAD_NO_BACKOFF"))) { var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30)); executionContext.Warning($"Back off {backOff.TotalSeconds} seconds before retry."); await Task.Delay(backOff); } } if (alwaysExtractTask) { Directory.CreateDirectory(HostContext.GetDirectory(WellKnownDirectory.TaskZips)); // Copy downloaded zip to the cache on disk for future extraction. executionContext.Debug($"Copying from {zipFile} to {taskZipPath}"); File.Copy(zipFile, taskZipPath); } // We need to extract the zip regardless of whether or not signing is enabled because the task.json metadata for the task is used in JobExtension.InitializeJob. // This is fine because we overwrite the contents at task run time. Directory.CreateDirectory(destDirectory); ExtractZip(zipFile, destDirectory); executionContext.Debug($"Task '{task.Name}' has been downloaded into '{(signingEnabled ? taskZipPath : destDirectory)}'."); Trace.Info("Finished getting task."); } finally { try { //if the temp folder wasn't moved -> wipe it if (Directory.Exists(tempDirectory)) { Trace.Verbose("Deleting task temp folder: {0}", tempDirectory); IOUtil.DeleteDirectory(tempDirectory, CancellationToken.None); // Don't cancel this cleanup and should be pretty fast. } } catch (Exception ex) { //it is not critical if we fail to delete the temp folder Trace.Warning("Failed to delete temp folder '{0}'. Exception: {1}", tempDirectory, ex); executionContext.Warning(StringUtil.Loc("FailedDeletingTempDirectory0Message1", tempDirectory, ex.Message)); } } }
// TODO: Updates legacy config. private TestHostContext Setup( [CallerMemberName] string name = "", BuildCleanOption?cleanOption = null, ExistingConfigKind existingConfigKind = ExistingConfigKind.None) { // Setup the host context. TestHostContext hc = new TestHostContext(this, name); // Create a random work path. var configStore = new Mock <IConfigurationStore>(); _workFolder = Path.Combine( Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"_work_{Path.GetRandomFileName()}"); var settings = new AgentSettings() { WorkFolder = _workFolder }; configStore.Setup(x => x.GetSettings()).Returns(settings); hc.SetSingleton <IConfigurationStore>(configStore.Object); // Setup the execution context. _ec = new Mock <IExecutionContext>(); List <string> warnings; _variables = new Variables(hc, new Dictionary <string, string>(), new List <MaskHint>(), out warnings); _variables.Set(Constants.Variables.System.CollectionId, CollectionId); _variables.Set(Constants.Variables.System.DefinitionId, DefinitionId); _variables.Set(Constants.Variables.Build.Clean, $"{cleanOption}"); _ec.Setup(x => x.Variables).Returns(_variables); // Store the expected tracking file path. _trackingFile = Path.Combine( _workFolder, Constants.Build.Path.SourceRootMappingDirectory, _ec.Object.Variables.System_CollectionId, _ec.Object.Variables.System_DefinitionId, Constants.Build.Path.TrackingConfigFile); // Setup the endpoint. _endpoint = new ServiceEndpoint() { Name = "Some endpoint name", Url = new Uri("http://contoso.visualstudio.com"), }; // Setup the source provider. _sourceProvider = new Mock <ISourceProvider>(); _sourceProvider .Setup(x => x.GetBuildDirectoryHashKey(_ec.Object, _endpoint)) .Returns(HashKey); hc.SetSingleton <ISourceProvider>(_sourceProvider.Object); // Store the existing config object. switch (existingConfigKind) { case ExistingConfigKind.Matching: _existingConfig = new TrackingConfig(_ec.Object, _endpoint, 1, HashKey); Assert.Equal("1", _existingConfig.BuildDirectory); break; case ExistingConfigKind.Nonmatching: _existingConfig = new TrackingConfig(_ec.Object, _endpoint, 2, NonmatchingHashKey); Assert.Equal("2", _existingConfig.BuildDirectory); break; case ExistingConfigKind.None: break; default: throw new NotSupportedException(); } // Store the new config object. if (existingConfigKind == ExistingConfigKind.Matching) { _newConfig = _existingConfig; } else { _newConfig = new TrackingConfig(_ec.Object, _endpoint, 3, HashKey); Assert.Equal("3", _newConfig.BuildDirectory); } // Setup the tracking manager. _trackingManager = new Mock <ITrackingManager>(); _trackingManager .Setup(x => x.LoadIfExists(_ec.Object, _trackingFile)) .Returns(_existingConfig); if (existingConfigKind == ExistingConfigKind.None || existingConfigKind == ExistingConfigKind.Nonmatching) { _trackingManager .Setup(x => x.Create(_ec.Object, _endpoint, HashKey, _trackingFile)) .Returns(_newConfig); if (existingConfigKind == ExistingConfigKind.Nonmatching) { _trackingManager .Setup(x => x.MarkForGarbageCollection(_ec.Object, _existingConfig)); } } else if (existingConfigKind == ExistingConfigKind.Matching) { _trackingManager .Setup(x => x.UpdateJobRunProperties(_ec.Object, _existingConfig, _trackingFile)); } else { throw new NotSupportedException(); } hc.SetSingleton <ITrackingManager>(_trackingManager.Object); // Setup the build directory manager. _buildDirectoryManager = new BuildDirectoryManager(); _buildDirectoryManager.Initialize(hc); return(hc); }
public async Task UnconfigureAsync(CommandSettings command) { string currentAction = StringUtil.Loc("UninstallingService"); try { //stop, uninstall service and remove service config file _term.WriteLine(currentAction); if (_store.IsServiceConfigured()) { var serviceControlManager = HostContext.GetService <IServiceControlManager>(); serviceControlManager.UnconfigureService(); _term.WriteLine(StringUtil.Loc("Success") + currentAction); } //delete agent from the server currentAction = StringUtil.Loc("UnregisteringAgent"); _term.WriteLine(currentAction); bool isConfigured = _store.IsConfigured(); bool hasCredentials = _store.HasCredentials(); if (isConfigured && hasCredentials) { AgentSettings settings = _store.GetSettings(); var credentialManager = HostContext.GetService <ICredentialManager>(); // Get the credentials var credProvider = GetCredentialProvider(command, settings.ServerUrl); VssCredentials creds = credProvider.GetVssCredentials(HostContext); Trace.Info("cred retrieved"); Uri uri = new Uri(settings.ServerUrl); VssConnection conn = ApiUtil.CreateConnection(uri, creds); var agentSvr = HostContext.GetService <IAgentServer>(); await agentSvr.ConnectAsync(conn); Trace.Info("Connect complete."); List <TaskAgent> agents = await agentSvr.GetAgentsAsync(settings.PoolId, settings.AgentName); if (agents.Count == 0) { _term.WriteLine(StringUtil.Loc("Skipping") + currentAction); } else { await agentSvr.DeleteAgentAsync(settings.PoolId, settings.AgentId); _term.WriteLine(StringUtil.Loc("Success") + currentAction); } } else { _term.WriteLine(StringUtil.Loc("MissingConfig")); } //delete credential config files currentAction = StringUtil.Loc("DeletingCredentials"); _term.WriteLine(currentAction); if (hasCredentials) { _store.DeleteCredential(); var keyManager = HostContext.GetService <IRSAKeyManager>(); keyManager.DeleteKey(); _term.WriteLine(StringUtil.Loc("Success") + currentAction); } else { _term.WriteLine(StringUtil.Loc("Skipping") + currentAction); } //delete settings config file currentAction = StringUtil.Loc("DeletingSettings"); _term.WriteLine(currentAction); if (isConfigured) { _store.DeleteSettings(); _term.WriteLine(StringUtil.Loc("Success") + currentAction); } else { _term.WriteLine(StringUtil.Loc("Skipping") + currentAction); } } catch (Exception) { _term.WriteLine(StringUtil.Loc("Failed") + currentAction); throw; } }
public override bool ConfigureService(AgentSettings settings, CommandSettings command) { Trace.Entering(); // TODO: add entering with info level. By default the error leve would be info. Config changes can get lost with this as entering is at Verbose level. For config all the logs should be logged. // TODO: Fix bug that exists in the legacy Windows agent where configuration using mirrored credentials causes an error, but the agent is still functional (after restarting). Mirrored credentials is a supported scenario and shouldn't manifest any errors. string logonPassword = string.Empty; NTAccount defaultServiceAccount = _windowsServiceHelper.GetDefaultServiceAccount(); _logonAccount = command.GetWindowsLogonAccount(defaultValue: defaultServiceAccount.ToString()); NativeWindowsServiceHelper.GetAccountSegments(_logonAccount, out _domainName, out _userName); if ((string.IsNullOrEmpty(_domainName) || _domainName.Equals(".", StringComparison.CurrentCultureIgnoreCase)) && !_logonAccount.Contains('@')) { _logonAccount = String.Format("{0}\\{1}", Environment.MachineName, _userName); } Trace.Info("LogonAccount after transforming: {0}, user: {1}, domain: {2}", _logonAccount, _userName, _domainName); if (!defaultServiceAccount.Equals(new NTAccount(_logonAccount)) && !NativeWindowsServiceHelper.IsWellKnownIdentity(_logonAccount)) { while (true) { logonPassword = command.GetWindowsLogonPassword(_logonAccount); // TODO: Fix this for unattended (should throw if not valid). // TODO: If account is locked there is no point in retrying, translate error to useful message if (_windowsServiceHelper.IsValidCredential(_domainName, _userName, logonPassword) || command.Unattended) { break; } Trace.Info("Invalid credential entered"); _term.WriteLine(StringUtil.Loc("InvalidWindowsCredential")); } } CalculateServiceName(settings, ServiceNamePattern, ServiceDisplayNamePattern); if (CheckServiceExists(ServiceName)) { _term.WriteLine(StringUtil.Loc("ServiceAleadyExists")); StopService(); UninstallService(ServiceName); } Trace.Info("Verifying if the account has LogonAsService permission"); if (!_windowsServiceHelper.CheckUserHasLogonAsServicePrivilege(_domainName, _userName)) { Trace.Info(StringUtil.Format("Account: {0} already has Logon As Service Privilege.", _logonAccount)); } else { if (!_windowsServiceHelper.GrantUserLogonAsServicePrivilage(_domainName, _userName)) { throw new InvalidOperationException(StringUtil.Loc("CanNotGrantPermission", _logonAccount)); } } _windowsServiceHelper.InstallService(ServiceName, ServiceDisplayName, _logonAccount, logonPassword); SaveServiceSettings(); // TODO: If its service identity add it to appropriate PoolGroup // TODO: Add registry key after installation return true; }
public override void GenerateScripts(AgentSettings settings) { }
public abstract bool ConfigureService(AgentSettings settings, CommandSettings command);
public async Task ConfigureAsync(CommandSettings command) { Trace.Info(nameof(ConfigureAsync)); if (IsConfigured()) { throw new InvalidOperationException(StringUtil.Loc("AlreadyConfiguredError")); } // TEE EULA bool acceptTeeEula = false; switch (Constants.Agent.Platform) { case Constants.OSPlatform.OSX: case Constants.OSPlatform.Linux: // Write the section header. WriteSection(StringUtil.Loc("EulasSectionHeader")); // Verify the EULA exists on disk in the expected location. string eulaFile = Path.Combine(IOUtil.GetExternalsPath(), Constants.Path.TeeDirectory, "license.html"); ArgUtil.File(eulaFile, nameof(eulaFile)); // Write elaborate verbiage about the TEE EULA. _term.WriteLine(StringUtil.Loc("TeeEula", eulaFile)); _term.WriteLine(); // Prompt to acccept the TEE EULA. acceptTeeEula = command.GetAcceptTeeEula(); break; case Constants.OSPlatform.Windows: break; default: throw new NotSupportedException(); } // TODO: Check if its running with elevated permission and stop early if its not // Loop getting url and creds until you can connect string serverUrl = null; ICredentialProvider credProvider = null; WriteSection(StringUtil.Loc("ConnectSectionHeader")); while (true) { // Get the URL serverUrl = command.GetUrl(); // Get the credentials credProvider = GetCredentialProvider(command, serverUrl); VssCredentials creds = credProvider.GetVssCredentials(HostContext); Trace.Info("cred retrieved"); try { // Validate can connect. await TestConnectAsync(serverUrl, creds); Trace.Info("Connect complete."); break; } catch (Exception e) when (!command.Unattended) { _term.WriteError(e); _term.WriteError(StringUtil.Loc("FailedToConnect")); // TODO: If the connection fails, shouldn't the URL/creds be cleared from the command line parser? Otherwise retry may be immediately attempted using the same values without prompting the user for new values. The same general problem applies to every retry loop during configure. } } // We want to use the native CSP of the platform for storage, so we use the RSACSP directly RSAParameters publicKey; var keyManager = HostContext.GetService<IRSAKeyManager>(); using (var rsa = keyManager.CreateKey()) { publicKey = rsa.ExportParameters(false); } // Loop getting agent name and pool string poolName = null; int poolId = 0; string agentName = null; WriteSection(StringUtil.Loc("RegisterAgentSectionHeader")); while (true) { poolName = command.GetPool(); try { poolId = await GetPoolId(poolName); } catch (Exception e) when (!command.Unattended) { _term.WriteError(e); } if (poolId > 0) { break; } _term.WriteError(StringUtil.Loc("FailedToFindPool")); } TaskAgent agent; while (true) { agentName = command.GetAgent(); // Get the system capabilities. // TODO: Hook up to ctrl+c cancellation token. // TODO: LOC _term.WriteLine("Scanning for tool capabilities."); Dictionary<string, string> systemCapabilities = await HostContext.GetService<ICapabilitiesManager>().GetCapabilitiesAsync( new AgentSettings { AgentName = agentName }, CancellationToken.None); // TODO: LOC _term.WriteLine("Connecting to the server."); agent = await GetAgent(agentName, poolId); if (agent != null) { if (command.GetReplace()) { agent.Authorization = new TaskAgentAuthorization { PublicKey = new TaskAgentPublicKey(publicKey.Exponent, publicKey.Modulus), }; // update - update instead of delete so we don't lose user capabilities etc... agent.Version = Constants.Agent.Version; foreach (KeyValuePair<string, string> capability in systemCapabilities) { agent.SystemCapabilities[capability.Key] = capability.Value ?? string.Empty; } try { agent = await _agentServer.UpdateAgentAsync(poolId, agent); _term.WriteLine(StringUtil.Loc("AgentReplaced")); break; } catch (Exception e) when (!command.Unattended) { _term.WriteError(e); _term.WriteError(StringUtil.Loc("FailedToReplaceAgent")); } } else { // TODO: ? } } else { agent = new TaskAgent(agentName) { Authorization = new TaskAgentAuthorization { PublicKey = new TaskAgentPublicKey(publicKey.Exponent, publicKey.Modulus), }, MaxParallelism = 1, Version = Constants.Agent.Version }; foreach (KeyValuePair<string, string> capability in systemCapabilities) { agent.SystemCapabilities[capability.Key] = capability.Value ?? string.Empty; } try { agent = await _agentServer.AddAgentAsync(poolId, agent); _term.WriteLine(StringUtil.Loc("AgentAddedSuccessfully")); break; } catch (Exception e) when (!command.Unattended) { _term.WriteError(e); _term.WriteError(StringUtil.Loc("AddAgentFailed")); } } } // See if the server supports our OAuth key exchange for credentials if (agent.Authorization != null && agent.Authorization.ClientId != Guid.Empty && agent.Authorization.AuthorizationUrl != null) { var credentialData = new CredentialData { Scheme = Constants.Configuration.OAuth, Data = { { "clientId", agent.Authorization.ClientId.ToString("D") }, { "authorizationUrl", agent.Authorization.AuthorizationUrl.AbsoluteUri }, }, }; // Save the negotiated OAuth credential data _store.SaveCredential(credentialData); } else { // Save the provided admin credential data for compat with existing agent _store.SaveCredential(credProvider.CredentialData); } // We will Combine() what's stored with root. Defaults to string a relative path string workFolder = command.GetWork(); string notificationPipeName = command.GetNotificationPipeName(); // Get Agent settings var settings = new AgentSettings { AcceptTeeEula = acceptTeeEula, AgentId = agent.Id, AgentName = agentName, NotificationPipeName = notificationPipeName, PoolId = poolId, PoolName = poolName, ServerUrl = serverUrl, WorkFolder = workFolder, }; _store.SaveSettings(settings); _term.WriteLine(StringUtil.Loc("SavedSettings", DateTime.UtcNow)); bool runAsService = false; if (Constants.Agent.Platform == Constants.OSPlatform.Windows) { runAsService = command.GetRunAsService(); } var serviceControlManager = HostContext.GetService<IServiceControlManager>(); serviceControlManager.GenerateScripts(settings); bool successfullyConfigured = false; if (runAsService) { Trace.Info("Configuring to run the agent as service"); successfullyConfigured = serviceControlManager.ConfigureService(settings, command); } if (runAsService && successfullyConfigured) { Trace.Info("Configuration was successful, trying to start the service"); serviceControlManager.StartService(); } }
public Task <TaskAgent> AddAgentAsync(AgentSettings agentSettings, TaskAgent agent, CommandSettings command) { return(_agentServer.AddAgentAsync(agentSettings.PoolId, agent)); }
public Task DeleteAgentAsync(AgentSettings agentSettings) { return(_agentServer.DeleteAgentAsync(agentSettings.PoolId, agentSettings.AgentId)); }
public async Task <TaskResult> RunAsync(AgentJobRequestMessage message, CancellationToken jobRequestCancellationToken) { // Validate parameters. Trace.Entering(); ArgUtil.NotNull(message, nameof(message)); ArgUtil.NotNull(message.Environment, nameof(message.Environment)); ArgUtil.NotNull(message.Environment.Variables, nameof(message.Environment.Variables)); ArgUtil.NotNull(message.Tasks, nameof(message.Tasks)); Trace.Info("Job ID {0}", message.JobId); if (message.Environment.Variables.ContainsKey(Constants.Variables.System.EnableAccessToken) && StringUtil.ConvertToBoolean(message.Environment.Variables[Constants.Variables.System.EnableAccessToken])) { // TODO: get access token use Util Method message.Environment.Variables[Constants.Variables.System.AccessToken] = message.Environment.SystemConnection.Authorization.Parameters["AccessToken"]; } // Make sure SystemConnection Url and Endpoint Url match Config Url base ReplaceConfigUriBaseInJobRequestMessage(message); // Setup the job server and job server queue. var jobServer = HostContext.GetService <IJobServer>(); var jobServerCredential = ApiUtil.GetVssCredential(message.Environment.SystemConnection); Uri jobServerUrl = message.Environment.SystemConnection.Url; Trace.Info($"Creating job server with URL: {jobServerUrl}"); // jobServerQueue is the throttling reporter. var jobServerQueue = HostContext.GetService <IJobServerQueue>(); var jobConnection = ApiUtil.CreateConnection(jobServerUrl, jobServerCredential, new DelegatingHandler[] { new ThrottlingReportHandler(jobServerQueue) }); await jobServer.ConnectAsync(jobConnection); jobServerQueue.Start(message); IExecutionContext jobContext = null; try { // Create the job execution context. jobContext = HostContext.CreateService <IExecutionContext>(); jobContext.InitializeJob(message, jobRequestCancellationToken); Trace.Info("Starting the job execution context."); jobContext.Start(); // Set agent version into ExecutionContext's variables dictionary. jobContext.Variables.Set(Constants.Variables.Agent.Version, Constants.Agent.Version); // Print agent version into log for better diagnostic experience jobContext.Output(StringUtil.Loc("AgentVersion", Constants.Agent.Version)); // Print proxy setting information for better diagnostic experience var proxyConfig = HostContext.GetService <IProxyConfiguration>(); if (!string.IsNullOrEmpty(proxyConfig.ProxyUrl)) { jobContext.Output(StringUtil.Loc("AgentRunningBehindProxy", proxyConfig.ProxyUrl)); } // Validate directory permissions. string workDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); Trace.Info($"Validating directory permissions for: '{workDirectory}'"); try { Directory.CreateDirectory(workDirectory); IOUtil.ValidateExecutePermission(workDirectory); } catch (Exception ex) { Trace.Error(ex); jobContext.Error(ex); return(jobContext.Complete(TaskResult.Failed)); } // Set agent variables. AgentSettings settings = HostContext.GetService <IConfigurationStore>().GetSettings(); jobContext.Variables.Set(Constants.Variables.Agent.Id, settings.AgentId.ToString(CultureInfo.InvariantCulture)); jobContext.Variables.Set(Constants.Variables.Agent.HomeDirectory, IOUtil.GetRootPath()); jobContext.Variables.Set(Constants.Variables.Agent.JobName, message.JobName); jobContext.Variables.Set(Constants.Variables.Agent.MachineName, Environment.MachineName); jobContext.Variables.Set(Constants.Variables.Agent.Name, settings.AgentName); jobContext.Variables.Set(Constants.Variables.Agent.RootDirectory, IOUtil.GetWorkPath(HostContext)); #if OS_WINDOWS jobContext.Variables.Set(Constants.Variables.Agent.ServerOMDirectory, Path.Combine(IOUtil.GetExternalsPath(), Constants.Path.ServerOMDirectory)); #endif jobContext.Variables.Set(Constants.Variables.Agent.WorkFolder, IOUtil.GetWorkPath(HostContext)); jobContext.Variables.Set(Constants.Variables.System.WorkFolder, IOUtil.GetWorkPath(HostContext)); // prefer task definitions url, then TFS collection url, then TFS account url var taskServer = HostContext.GetService <ITaskServer>(); Uri taskServerUri = null; if (!string.IsNullOrEmpty(jobContext.Variables.System_TaskDefinitionsUri)) { taskServerUri = new Uri(jobContext.Variables.System_TaskDefinitionsUri); } else if (!string.IsNullOrEmpty(jobContext.Variables.System_TFCollectionUrl)) { taskServerUri = new Uri(jobContext.Variables.System_TFCollectionUrl); } var taskServerCredential = ApiUtil.GetVssCredential(message.Environment.SystemConnection); if (taskServerUri != null) { Trace.Info($"Creating task server with {taskServerUri}"); await taskServer.ConnectAsync(ApiUtil.CreateConnection(taskServerUri, taskServerCredential)); } if (taskServerUri == null || !await taskServer.TaskDefinitionEndpointExist(jobRequestCancellationToken)) { Trace.Info($"Can't determine task download url from JobMessage or the endpoint doesn't exist."); var configStore = HostContext.GetService <IConfigurationStore>(); taskServerUri = new Uri(configStore.GetSettings().ServerUrl); Trace.Info($"Recreate task server with configuration server url: {taskServerUri}"); await taskServer.ConnectAsync(ApiUtil.CreateConnection(taskServerUri, taskServerCredential)); } // Expand the endpoint data values. foreach (ServiceEndpoint endpoint in jobContext.Endpoints) { jobContext.Variables.ExpandValues(target: endpoint.Data); VarUtil.ExpandEnvironmentVariables(HostContext, target: endpoint.Data); } // Get the job extensions. Trace.Info("Getting job extensions."); string hostType = jobContext.Variables.System_HostType; var extensionManager = HostContext.GetService <IExtensionManager>(); IJobExtension[] extensions = (extensionManager.GetExtensions <IJobExtension>() ?? new List <IJobExtension>()) .Where(x => string.Equals(x.HostType, hostType, StringComparison.OrdinalIgnoreCase)) .ToArray(); // Add the prepare steps. Trace.Info("Adding job prepare extensions."); List <IStep> steps = new List <IStep>(); foreach (IJobExtension extension in extensions) { if (extension.PrepareStep != null) { Trace.Verbose($"Adding {extension.GetType().Name}.{nameof(extension.PrepareStep)}."); extension.PrepareStep.ExecutionContext = jobContext.CreateChild(Guid.NewGuid(), extension.PrepareStep.DisplayName); steps.Add(extension.PrepareStep); } } // Add the task steps. Trace.Info("Adding tasks."); foreach (TaskInstance taskInstance in message.Tasks) { Trace.Verbose($"Adding {taskInstance.DisplayName}."); var taskRunner = HostContext.CreateService <ITaskRunner>(); taskRunner.ExecutionContext = jobContext.CreateChild(taskInstance.InstanceId, taskInstance.DisplayName); taskRunner.TaskInstance = taskInstance; steps.Add(taskRunner); } // Add the finally steps. Trace.Info("Adding job finally extensions."); foreach (IJobExtension extension in extensions) { if (extension.FinallyStep != null) { Trace.Verbose($"Adding {extension.GetType().Name}.{nameof(extension.FinallyStep)}."); extension.FinallyStep.ExecutionContext = jobContext.CreateChild(Guid.NewGuid(), extension.FinallyStep.DisplayName); steps.Add(extension.FinallyStep); } } // Download tasks if not already in the cache Trace.Info("Downloading task definitions."); var taskManager = HostContext.GetService <ITaskManager>(); try { await taskManager.DownloadAsync(jobContext, message.Tasks); } catch (OperationCanceledException ex) { // set the job to canceled Trace.Error($"Caught exception: {ex}"); jobContext.Error(ex); return(jobContext.Complete(TaskResult.Canceled)); } catch (Exception ex) { // Log the error and fail the job. Trace.Error($"Caught exception from {nameof(TaskManager)}: {ex}"); jobContext.Error(ex); return(jobContext.Complete(TaskResult.Failed)); } // Run the steps. var stepsRunner = HostContext.GetService <IStepsRunner>(); try { await stepsRunner.RunAsync(jobContext, steps); } catch (OperationCanceledException ex) { // set the job to canceled Trace.Error($"Caught exception: {ex}"); jobContext.Error(ex); return(jobContext.Complete(TaskResult.Canceled)); } catch (Exception ex) { // Log the error and fail the job. Trace.Error($"Caught exception from {nameof(StepsRunner)}: {ex}"); jobContext.Error(ex); return(jobContext.Complete(TaskResult.Failed)); } Trace.Info($"Job result: {jobContext.Result}"); // Complete the job. Trace.Info("Completing the job execution context."); return(jobContext.Complete()); } finally { // Drain the job server queue. if (jobServerQueue != null) { try { Trace.Info("Shutting down the job server queue."); await jobServerQueue.ShutdownAsync(); } catch (Exception ex) { Trace.Error($"Caught exception from {nameof(JobServerQueue)}.{nameof(jobServerQueue.ShutdownAsync)}: {ex}"); } } } }
private TestHostContext CreateTestContext([CallerMemberName] String testName = "") { var hc = new TestHostContext(this, testName); _jobEc = new Agent.Worker.ExecutionContext(); _config = new Mock <IConfigurationStore>(); _extensions = new Mock <IExtensionManager>(); _jobExtension = new Mock <IJobExtension>(); _jobServer = new Mock <IJobServer>(); _jobServerQueue = new Mock <IJobServerQueue>(); _proxyConfig = new Mock <IVstsAgentWebProxy>(); _cert = new Mock <IAgentCertificateManager>(); _taskServer = new Mock <ITaskServer>(); _stepRunner = new Mock <IStepsRunner>(); _logger = new Mock <IPagingLogger>(); _temp = new Mock <ITempDirectoryManager>(); _diagnosticLogManager = new Mock <IDiagnosticLogManager>(); if (_tokenSource != null) { _tokenSource.Dispose(); _tokenSource = null; } _tokenSource = new CancellationTokenSource(); var expressionManager = new ExpressionManager(); expressionManager.Initialize(hc); hc.SetSingleton <IExpressionManager>(expressionManager); _jobRunner = new JobRunner(); _jobRunner.Initialize(hc); TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference(); TimelineReference timeline = new Timeline(Guid.NewGuid()); JobEnvironment environment = new JobEnvironment(); environment.Variables[Constants.Variables.System.Culture] = "en-US"; environment.SystemConnection = new ServiceEndpoint() { Url = new Uri("https://test.visualstudio.com"), Authorization = new EndpointAuthorization() { Scheme = "Test", } }; environment.SystemConnection.Authorization.Parameters["AccessToken"] = "token"; List <TaskInstance> tasks = new List <TaskInstance>(); Guid JobId = Guid.NewGuid(); _message = new AgentJobRequestMessage(plan, timeline, JobId, testName, testName, environment, tasks); _extensions.Setup(x => x.GetExtensions <IJobExtension>()). Returns(new[] { _jobExtension.Object }.ToList()); _initResult.PreJobSteps.Clear(); _initResult.JobSteps.Clear(); _initResult.PostJobStep.Clear(); _jobExtension.Setup(x => x.InitializeJob(It.IsAny <IExecutionContext>(), It.IsAny <AgentJobRequestMessage>())). Returns(Task.FromResult(_initResult)); _jobExtension.Setup(x => x.HostType) .Returns <string>(null); _proxyConfig.Setup(x => x.ProxyAddress) .Returns(string.Empty); var settings = new AgentSettings { AgentId = 1, AgentName = "agent1", ServerUrl = "https://test.visualstudio.com", WorkFolder = "_work", }; _config.Setup(x => x.GetSettings()) .Returns(settings); _logger.Setup(x => x.Setup(It.IsAny <Guid>(), It.IsAny <Guid>())); hc.SetSingleton(_config.Object); hc.SetSingleton(_jobServer.Object); hc.SetSingleton(_jobServerQueue.Object); hc.SetSingleton(_proxyConfig.Object); hc.SetSingleton(_cert.Object); hc.SetSingleton(_taskServer.Object); hc.SetSingleton(_stepRunner.Object); hc.SetSingleton(_extensions.Object); hc.SetSingleton(_temp.Object); hc.SetSingleton(_diagnosticLogManager.Object); hc.EnqueueInstance <IExecutionContext>(_jobEc); hc.EnqueueInstance <IPagingLogger>(_logger.Object); return(hc); }
public void UpdateAgentSetting(AgentSettings settings) { settings.MachineGroupId = _machineGroupId; settings.ProjectName = _projectName; }
public async Task <int> ExecuteCommand(CommandSettings command) { try { var agentWebProxy = HostContext.GetService <IVstsAgentWebProxy>(); var agentCertManager = HostContext.GetService <IAgentCertificateManager>(); ApiUtil.InitializeVssClientSettings(agentWebProxy, agentCertManager); _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; // Local run if (command.LocalRun) { var localManager = HostContext.GetService <ILocalRunner>(); return(await localManager.LocalRunAsync(command, HostContext.AgentShutdownToken)); } 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 async Task UploadDiagnosticLogsAsync(IExecutionContext executionContext, Pipelines.AgentJobRequestMessage message, DateTime jobStartTimeUtc) { executionContext.Debug("Starting diagnostic file upload."); // Setup folders // \_layout\_work\_temp\[jobname-support] executionContext.Debug("Setting up diagnostic log folders."); string tempDirectory = HostContext.GetDirectory(WellKnownDirectory.Temp); ArgUtil.Directory(tempDirectory, nameof(tempDirectory)); string supportRootFolder = Path.Combine(tempDirectory, message.JobName + "-support"); Directory.CreateDirectory(supportRootFolder); // \_layout\_work\_temp\[jobname-support]\files executionContext.Debug("Creating diagnostic log files folder."); string supportFilesFolder = Path.Combine(supportRootFolder, "files"); Directory.CreateDirectory(supportFilesFolder); // Create the environment file // \_layout\_work\_temp\[jobname-support]\files\environment.txt var configurationStore = HostContext.GetService <IConfigurationStore>(); AgentSettings settings = configurationStore.GetSettings(); int agentId = settings.AgentId; string agentName = settings.AgentName; int poolId = settings.PoolId; executionContext.Debug("Creating diagnostic log environment file."); string environmentFile = Path.Combine(supportFilesFolder, "environment.txt"); string content = await GetEnvironmentContent(agentId, agentName, message.Steps); File.WriteAllText(environmentFile, content); // Create the capabilities file var capabilitiesManager = HostContext.GetService <ICapabilitiesManager>(); Dictionary <string, string> capabilities = await capabilitiesManager.GetCapabilitiesAsync(configurationStore.GetSettings(), default(CancellationToken)); executionContext.Debug("Creating capabilities file."); string capabilitiesFile = Path.Combine(supportFilesFolder, "capabilities.txt"); string capabilitiesContent = GetCapabilitiesContent(capabilities); File.WriteAllText(capabilitiesFile, capabilitiesContent); // Copy worker diag log files List <string> workerDiagLogFiles = GetWorkerDiagLogFiles(HostContext.GetDirectory(WellKnownDirectory.Diag), jobStartTimeUtc); executionContext.Debug($"Copying {workerDiagLogFiles.Count()} worker diag logs."); foreach (string workerLogFile in workerDiagLogFiles) { ArgUtil.File(workerLogFile, nameof(workerLogFile)); string destination = Path.Combine(supportFilesFolder, Path.GetFileName(workerLogFile)); File.Copy(workerLogFile, destination); } // Copy agent diag log files List <string> agentDiagLogFiles = GetAgentDiagLogFiles(HostContext.GetDirectory(WellKnownDirectory.Diag), jobStartTimeUtc); executionContext.Debug($"Copying {agentDiagLogFiles.Count()} agent diag logs."); foreach (string agentLogFile in agentDiagLogFiles) { ArgUtil.File(agentLogFile, nameof(agentLogFile)); string destination = Path.Combine(supportFilesFolder, Path.GetFileName(agentLogFile)); File.Copy(agentLogFile, destination); } executionContext.Debug("Zipping diagnostic files."); string buildNumber = executionContext.Variables.Build_Number ?? "UnknownBuildNumber"; string buildName = $"Build {buildNumber}"; string phaseName = executionContext.Variables.System_PhaseDisplayName ?? "UnknownPhaseName"; // zip the files string diagnosticsZipFileName = $"{buildName}-{phaseName}.zip"; string diagnosticsZipFilePath = Path.Combine(supportRootFolder, diagnosticsZipFileName); ZipFile.CreateFromDirectory(supportFilesFolder, diagnosticsZipFilePath); // upload the json metadata file executionContext.Debug("Uploading diagnostic metadata file."); string metadataFileName = $"diagnostics-{buildName}-{phaseName}.json"; string metadataFilePath = Path.Combine(supportFilesFolder, metadataFileName); string phaseResult = GetTaskResultAsString(executionContext.Result); IOUtil.SaveObject(new DiagnosticLogMetadata(agentName, agentId, poolId, phaseName, diagnosticsZipFileName, phaseResult), metadataFilePath); executionContext.QueueAttachFile(type: CoreAttachmentType.DiagnosticLog, name: metadataFileName, filePath: metadataFilePath); executionContext.QueueAttachFile(type: CoreAttachmentType.DiagnosticLog, name: diagnosticsZipFileName, filePath: diagnosticsZipFilePath); executionContext.Debug("Diagnostic file upload complete."); }
public async Task ConfigureAsync(CommandSettings command) { Trace.Info(nameof(ConfigureAsync)); if (IsConfigured()) { throw new InvalidOperationException(StringUtil.Loc("AlreadyConfiguredError")); } // TEE EULA bool acceptTeeEula = false; switch (Constants.Agent.Platform) { case Constants.OSPlatform.OSX: case Constants.OSPlatform.Linux: // Write the section header. WriteSection(StringUtil.Loc("EulasSectionHeader")); // Verify the EULA exists on disk in the expected location. string eulaFile = Path.Combine(IOUtil.GetExternalsPath(), Constants.Path.TeeDirectory, "license.html"); ArgUtil.File(eulaFile, nameof(eulaFile)); // Write elaborate verbiage about the TEE EULA. _term.WriteLine(StringUtil.Loc("TeeEula", eulaFile)); _term.WriteLine(); // Prompt to acccept the TEE EULA. acceptTeeEula = command.GetAcceptTeeEula(); break; case Constants.OSPlatform.Windows: break; default: throw new NotSupportedException(); } // TODO: Check if its running with elevated permission and stop early if its not // Loop getting url and creds until you can connect string serverUrl = null; ICredentialProvider credProvider = null; WriteSection(StringUtil.Loc("ConnectSectionHeader")); while (true) { // Get the URL serverUrl = command.GetUrl(); // Get the credentials credProvider = GetCredentialProvider(command, serverUrl); VssCredentials creds = credProvider.GetVssCredentials(HostContext); Trace.Info("cred retrieved"); try { // Validate can connect. await TestConnectAsync(serverUrl, creds); Trace.Info("Connect complete."); break; } catch (Exception e) when(!command.Unattended) { _term.WriteError(e); _term.WriteError(StringUtil.Loc("FailedToConnect")); // TODO: If the connection fails, shouldn't the URL/creds be cleared from the command line parser? Otherwise retry may be immediately attempted using the same values without prompting the user for new values. The same general problem applies to every retry loop during configure. } } // We want to use the native CSP of the platform for storage, so we use the RSACSP directly RSAParameters publicKey; var keyManager = HostContext.GetService <IRSAKeyManager>(); using (var rsa = keyManager.CreateKey()) { publicKey = rsa.ExportParameters(false); } // Loop getting agent name and pool string poolName = null; int poolId = 0; string agentName = null; WriteSection(StringUtil.Loc("RegisterAgentSectionHeader")); while (true) { poolName = command.GetPool(); try { poolId = await GetPoolId(poolName); } catch (Exception e) when(!command.Unattended) { _term.WriteError(e); } if (poolId > 0) { break; } _term.WriteError(StringUtil.Loc("FailedToFindPool")); } TaskAgent agent; while (true) { agentName = command.GetAgentName(); // Get the system capabilities. // TODO: Hook up to ctrl+c cancellation token. _term.WriteLine(StringUtil.Loc("ScanToolCapabilities")); Dictionary <string, string> systemCapabilities = await HostContext.GetService <ICapabilitiesManager>().GetCapabilitiesAsync( new AgentSettings { AgentName = agentName }, CancellationToken.None); _term.WriteLine(StringUtil.Loc("ConnectToServer")); agent = await GetAgent(agentName, poolId); if (agent != null) { if (command.GetReplace()) { agent.Authorization = new TaskAgentAuthorization { PublicKey = new TaskAgentPublicKey(publicKey.Exponent, publicKey.Modulus), }; // update - update instead of delete so we don't lose user capabilities etc... agent.Version = Constants.Agent.Version; foreach (KeyValuePair <string, string> capability in systemCapabilities) { agent.SystemCapabilities[capability.Key] = capability.Value ?? string.Empty; } try { agent = await _agentServer.UpdateAgentAsync(poolId, agent); _term.WriteLine(StringUtil.Loc("AgentReplaced")); break; } catch (Exception e) when(!command.Unattended) { _term.WriteError(e); _term.WriteError(StringUtil.Loc("FailedToReplaceAgent")); } } else { // TODO: ? } } else { agent = new TaskAgent(agentName) { Authorization = new TaskAgentAuthorization { PublicKey = new TaskAgentPublicKey(publicKey.Exponent, publicKey.Modulus), }, MaxParallelism = 1, Version = Constants.Agent.Version }; foreach (KeyValuePair <string, string> capability in systemCapabilities) { agent.SystemCapabilities[capability.Key] = capability.Value ?? string.Empty; } try { agent = await _agentServer.AddAgentAsync(poolId, agent); _term.WriteLine(StringUtil.Loc("AgentAddedSuccessfully")); break; } catch (Exception e) when(!command.Unattended) { _term.WriteError(e); _term.WriteError(StringUtil.Loc("AddAgentFailed")); } } } // See if the server supports our OAuth key exchange for credentials if (agent.Authorization != null && agent.Authorization.ClientId != Guid.Empty && agent.Authorization.AuthorizationUrl != null) { var credentialData = new CredentialData { Scheme = Constants.Configuration.OAuth, Data = { { "clientId", agent.Authorization.ClientId.ToString("D") }, { "authorizationUrl", agent.Authorization.AuthorizationUrl.AbsoluteUri }, }, }; // Save the negotiated OAuth credential data _store.SaveCredential(credentialData); } else { // Save the provided admin credential data for compat with existing agent _store.SaveCredential(credProvider.CredentialData); } // Testing agent connection, detect any protential connection issue, like local clock skew that cause OAuth token expired. _term.WriteLine(StringUtil.Loc("TestAgentConnection")); var credMgr = HostContext.GetService <ICredentialManager>(); VssCredentials credential = credMgr.LoadCredentials(); VssConnection conn = ApiUtil.CreateConnection(new Uri(serverUrl), credential); var agentSvr = HostContext.GetService <IAgentServer>(); try { await agentSvr.ConnectAsync(conn); } catch (VssOAuthTokenRequestException ex) when(ex.Message.Contains("Current server time is")) { // there are two exception messages server send that indicate clock skew. // 1. The bearer token expired on {jwt.ValidTo}. Current server time is {DateTime.UtcNow}. // 2. The bearer token is not valid until {jwt.ValidFrom}. Current server time is {DateTime.UtcNow}. Trace.Error("Catch exception during test agent connection."); Trace.Error(ex); throw new Exception(StringUtil.Loc("LocalClockSkewed")); } // We will Combine() what's stored with root. Defaults to string a relative path string workFolder = command.GetWork(); string notificationPipeName = command.GetNotificationPipeName(); // Get Agent settings var settings = new AgentSettings { AcceptTeeEula = acceptTeeEula, AgentId = agent.Id, AgentName = agentName, NotificationPipeName = notificationPipeName, PoolId = poolId, PoolName = poolName, ServerUrl = serverUrl, WorkFolder = workFolder, }; _store.SaveSettings(settings); _term.WriteLine(StringUtil.Loc("SavedSettings", DateTime.UtcNow)); bool runAsService = false; if (Constants.Agent.Platform == Constants.OSPlatform.Windows) { runAsService = command.GetRunAsService(); if (runAsService) { if (!new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) { Trace.Error("Needs Administrator privileges for configure agent as windows service."); throw new SecurityException(StringUtil.Loc("NeedAdminForConfigAgentWinService")); } } } var serviceControlManager = HostContext.GetService <IServiceControlManager>(); serviceControlManager.GenerateScripts(settings); bool successfullyConfigured = false; if (runAsService) { Trace.Info("Configuring to run the agent as service"); successfullyConfigured = serviceControlManager.ConfigureService(settings, command); } if (runAsService && successfullyConfigured) { Trace.Info("Configuration was successful, trying to start the service"); serviceControlManager.StartService(); } }
public TrackingConfig Create( IExecutionContext executionContext, ServiceEndpoint endpoint, string hashKey, string file, bool overrideBuildDirectory) { Trace.Entering(); // Get or create the top-level tracking config. TopLevelTrackingConfig topLevelConfig; string topLevelFile = Path.Combine( IOUtil.GetWorkPath(HostContext), Constants.Build.Path.SourceRootMappingDirectory, Constants.Build.Path.TopLevelTrackingConfigFile); Trace.Verbose($"Loading top-level tracking config if exists: {topLevelFile}"); if (!File.Exists(topLevelFile)) { topLevelConfig = new TopLevelTrackingConfig(); } else { topLevelConfig = JsonConvert.DeserializeObject <TopLevelTrackingConfig>(File.ReadAllText(topLevelFile)); if (topLevelConfig == null) { executionContext.Warning($"Rebuild corruptted top-level tracking configure file {topLevelFile}."); // save the corruptted file in case we need to investigate more. File.Copy(topLevelFile, $"{topLevelFile}.corruptted", true); topLevelConfig = new TopLevelTrackingConfig(); DirectoryInfo workDir = new DirectoryInfo(HostContext.GetDirectory(WellKnownDirectory.Work)); foreach (var dir in workDir.EnumerateDirectories()) { // we scan the entire _work directory and find the directory with the highest integer number. if (int.TryParse(dir.Name, NumberStyles.Integer, CultureInfo.InvariantCulture, out int lastBuildNumber) && lastBuildNumber > topLevelConfig.LastBuildDirectoryNumber) { topLevelConfig.LastBuildDirectoryNumber = lastBuildNumber; } } } } // Determine the build directory. if (overrideBuildDirectory) { // This should only occur during hosted builds. This was added due to TFVC. // TFVC does not allow a local path for a single machine to be mapped in multiple // workspaces. The machine name for a hosted images is not unique. // // So if a customer is running two hosted builds at the same time, they could run // into the local mapping conflict. // // The workaround is to force the build directory to be different across all concurrent // hosted builds (for TFVC). The agent ID will be unique across all concurrent hosted // builds so that can safely be used as the build directory. ArgUtil.Equal(default(int), topLevelConfig.LastBuildDirectoryNumber, nameof(topLevelConfig.LastBuildDirectoryNumber)); var configurationStore = HostContext.GetService <IConfigurationStore>(); AgentSettings settings = configurationStore.GetSettings(); topLevelConfig.LastBuildDirectoryNumber = settings.AgentId; } else { topLevelConfig.LastBuildDirectoryNumber++; } // Update the top-level tracking config. topLevelConfig.LastBuildDirectoryCreatedOn = DateTimeOffset.Now; WriteToFile(topLevelFile, topLevelConfig); // Create the new tracking config. TrackingConfig config = new TrackingConfig( executionContext, endpoint, topLevelConfig.LastBuildDirectoryNumber, hashKey); WriteToFile(file, config); return(config); }
public void ConfigureService(AgentSettings settings, CommandSettings command) { Trace.Entering(); if (!_windowsServiceHelper.IsRunningInElevatedMode()) { Trace.Error("Needs Administrator privileges for configure agent as windows service."); throw new SecurityException(StringUtil.Loc("NeedAdminForConfigAgentWinService")); } // TODO: Fix bug that exists in the legacy Windows agent where configuration using mirrored credentials causes an error, but the agent is still functional (after restarting). Mirrored credentials is a supported scenario and shouldn't manifest any errors. // We use NetworkService as default account for build and release agent // We use Local System as default account for deployment agent, deployment pool agent bool isDeploymentGroupScenario = command.DeploymentGroup || command.DeploymentPool; NTAccount defaultServiceAccount = isDeploymentGroupScenario ? _windowsServiceHelper.GetDefaultAdminServiceAccount() : _windowsServiceHelper.GetDefaultServiceAccount(); string logonAccount = command.GetWindowsLogonAccount(defaultValue: defaultServiceAccount.ToString(), descriptionMsg: StringUtil.Loc("WindowsLogonAccountNameDescription")); string domainName; string userName; GetAccountSegments(logonAccount, out domainName, out userName); if ((string.IsNullOrEmpty(domainName) || domainName.Equals(".", StringComparison.CurrentCultureIgnoreCase)) && !logonAccount.Contains('@')) { logonAccount = String.Format("{0}\\{1}", Environment.MachineName, userName); domainName = Environment.MachineName; } Trace.Info("LogonAccount after transforming: {0}, user: {1}, domain: {2}", logonAccount, userName, domainName); string logonPassword = string.Empty; if (!defaultServiceAccount.Equals(new NTAccount(logonAccount)) && !NativeWindowsServiceHelper.IsWellKnownIdentity(logonAccount)) { while (true) { logonPassword = command.GetWindowsLogonPassword(logonAccount); if (_windowsServiceHelper.IsValidCredential(domainName, userName, logonPassword)) { Trace.Info("Credential validation succeed"); break; } else { if (!command.Unattended) { Trace.Info("Invalid credential entered"); _term.WriteLine(StringUtil.Loc("InvalidWindowsCredential")); } else { throw new SecurityException(StringUtil.Loc("InvalidWindowsCredential")); } } } } string serviceName; string serviceDisplayName; CalculateServiceName(settings, ServiceNamePattern, ServiceDisplayNamePattern, out serviceName, out serviceDisplayName); if (_windowsServiceHelper.IsServiceExists(serviceName)) { _term.WriteLine(StringUtil.Loc("ServiceAlreadyExists", serviceName)); _windowsServiceHelper.UninstallService(serviceName); } Trace.Info("Verifying if the account has LogonAsService permission"); if (_windowsServiceHelper.IsUserHasLogonAsServicePrivilege(domainName, userName)) { Trace.Info($"Account: {logonAccount} already has Logon As Service Privilege."); } else { if (!_windowsServiceHelper.GrantUserLogonAsServicePrivilage(domainName, userName)) { throw new InvalidOperationException(StringUtil.Loc("CanNotGrantPermission", logonAccount)); } } Trace.Info("Create local group and grant folder permission to service logon account."); GrantDirectoryPermissionForAccount(logonAccount); // install service. _windowsServiceHelper.InstallService(serviceName, serviceDisplayName, logonAccount, logonPassword); // create .service file with service name. SaveServiceSettings(serviceName); // Add registry key after installation _windowsServiceHelper.CreateVstsAgentRegistryKey(); Trace.Info("Configuration was successful, trying to start the service"); _windowsServiceHelper.StartService(serviceName); }
private bool ShouldOverrideBuildDirectory(IList <RepositoryResource> repositories, AgentSettings settings) { if (repositories?.Count == 1 && repositories[0].Type == RepositoryTypes.Tfvc) { return(settings.IsHosted); } else { return(false); } }
public async Task <TaskResult> RunAsync(Pipelines.AgentJobRequestMessage message, CancellationToken jobRequestCancellationToken) { // Validate parameters. Trace.Entering(); ArgUtil.NotNull(message, nameof(message)); ArgUtil.NotNull(message.Resources, nameof(message.Resources)); ArgUtil.NotNull(message.Variables, nameof(message.Variables)); ArgUtil.NotNull(message.Steps, nameof(message.Steps)); Trace.Info("Job ID {0}", message.JobId); DateTime jobStartTimeUtc = DateTime.UtcNow; // Agent.RunMode RunMode runMode; if (message.Variables.ContainsKey(Constants.Variables.Agent.RunMode) && Enum.TryParse(message.Variables[Constants.Variables.Agent.RunMode].Value, ignoreCase: true, result: out runMode) && runMode == RunMode.Local) { HostContext.RunMode = runMode; } ServiceEndpoint systemConnection = message.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase)); // System.AccessToken if (message.Variables.ContainsKey(Constants.Variables.System.EnableAccessToken) && StringUtil.ConvertToBoolean(message.Variables[Constants.Variables.System.EnableAccessToken].Value)) { message.Variables[Constants.Variables.System.AccessToken] = new VariableValue(systemConnection.Authorization.Parameters["AccessToken"], false); } // back compat TfsServerUrl message.Variables[Constants.Variables.System.TFServerUrl] = systemConnection.Url.AbsoluteUri; // Make sure SystemConnection Url and Endpoint Url match Config Url base for OnPremises server if (!message.Variables.ContainsKey(Constants.Variables.System.ServerType)) { if (!UrlUtil.IsHosted(systemConnection.Url.AbsoluteUri)) // TODO: remove this after TFS/RM move to M133 { ReplaceConfigUriBaseInJobRequestMessage(message); } } else if (string.Equals(message.Variables[Constants.Variables.System.ServerType]?.Value, "OnPremises", StringComparison.OrdinalIgnoreCase)) { ReplaceConfigUriBaseInJobRequestMessage(message); } // Setup the job server and job server queue. var jobServer = HostContext.GetService <IJobServer>(); VssCredentials jobServerCredential = ApiUtil.GetVssCredential(systemConnection); Uri jobServerUrl = systemConnection.Url; Trace.Info($"Creating job server with URL: {jobServerUrl}"); // jobServerQueue is the throttling reporter. _jobServerQueue = HostContext.GetService <IJobServerQueue>(); VssConnection jobConnection = ApiUtil.CreateConnection(jobServerUrl, jobServerCredential, new DelegatingHandler[] { new ThrottlingReportHandler(_jobServerQueue) }); await jobServer.ConnectAsync(jobConnection); _jobServerQueue.Start(message); IExecutionContext jobContext = null; CancellationTokenRegistration?agentShutdownRegistration = null; try { // Create the job execution context. jobContext = HostContext.CreateService <IExecutionContext>(); jobContext.InitializeJob(message, jobRequestCancellationToken); Trace.Info("Starting the job execution context."); jobContext.Start(); jobContext.Section(StringUtil.Loc("StepStarting", message.JobDisplayName)); agentShutdownRegistration = HostContext.AgentShutdownToken.Register(() => { // log an issue, then agent get shutdown by Ctrl-C or Ctrl-Break. // the server will use Ctrl-Break to tells the agent that operating system is shutting down. string errorMessage; switch (HostContext.AgentShutdownReason) { case ShutdownReason.UserCancelled: errorMessage = StringUtil.Loc("UserShutdownAgent"); break; case ShutdownReason.OperatingSystemShutdown: errorMessage = StringUtil.Loc("OperatingSystemShutdown", Environment.MachineName); break; default: throw new ArgumentException(HostContext.AgentShutdownReason.ToString(), nameof(HostContext.AgentShutdownReason)); } jobContext.AddIssue(new Issue() { Type = IssueType.Error, Message = errorMessage }); }); // Set agent version variable. jobContext.Variables.Set(Constants.Variables.Agent.Version, Constants.Agent.Version); jobContext.Output(StringUtil.Loc("AgentVersion", Constants.Agent.Version)); // Print proxy setting information for better diagnostic experience var agentWebProxy = HostContext.GetService <IVstsAgentWebProxy>(); if (!string.IsNullOrEmpty(agentWebProxy.ProxyAddress)) { jobContext.Output(StringUtil.Loc("AgentRunningBehindProxy", agentWebProxy.ProxyAddress)); } // Validate directory permissions. string workDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); Trace.Info($"Validating directory permissions for: '{workDirectory}'"); try { Directory.CreateDirectory(workDirectory); IOUtil.ValidateExecutePermission(workDirectory); } catch (Exception ex) { Trace.Error(ex); jobContext.Error(ex); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } // Set agent variables. AgentSettings settings = HostContext.GetService <IConfigurationStore>().GetSettings(); jobContext.Variables.Set(Constants.Variables.Agent.Id, settings.AgentId.ToString(CultureInfo.InvariantCulture)); jobContext.Variables.Set(Constants.Variables.Agent.HomeDirectory, HostContext.GetDirectory(WellKnownDirectory.Root)); jobContext.Variables.Set(Constants.Variables.Agent.JobName, message.JobDisplayName); jobContext.Variables.Set(Constants.Variables.Agent.MachineName, Environment.MachineName); jobContext.Variables.Set(Constants.Variables.Agent.Name, settings.AgentName); jobContext.Variables.Set(Constants.Variables.Agent.OS, VarUtil.OS); jobContext.Variables.Set(Constants.Variables.Agent.RootDirectory, IOUtil.GetWorkPath(HostContext)); #if OS_WINDOWS jobContext.Variables.Set(Constants.Variables.Agent.ServerOMDirectory, Path.Combine(IOUtil.GetExternalsPath(), Constants.Path.ServerOMDirectory)); #endif jobContext.Variables.Set(Constants.Variables.Agent.WorkFolder, IOUtil.GetWorkPath(HostContext)); jobContext.Variables.Set(Constants.Variables.System.WorkFolder, IOUtil.GetWorkPath(HostContext)); string toolsDirectory = Environment.GetEnvironmentVariable("AGENT_TOOLSDIRECTORY") ?? Environment.GetEnvironmentVariable(Constants.Variables.Agent.ToolsDirectory); if (string.IsNullOrEmpty(toolsDirectory)) { toolsDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), Constants.Path.ToolDirectory); Directory.CreateDirectory(toolsDirectory); } else { Trace.Info($"Set tool cache directory base on environment: '{toolsDirectory}'"); Directory.CreateDirectory(toolsDirectory); } jobContext.Variables.Set(Constants.Variables.Agent.ToolsDirectory, toolsDirectory); // Setup TEMP directories _tempDirectoryManager = HostContext.GetService <ITempDirectoryManager>(); _tempDirectoryManager.InitializeTempDirectory(jobContext); // todo: task server can throw. try/catch and fail job gracefully. // prefer task definitions url, then TFS collection url, then TFS account url var taskServer = HostContext.GetService <ITaskServer>(); Uri taskServerUri = null; if (!string.IsNullOrEmpty(jobContext.Variables.System_TaskDefinitionsUri)) { taskServerUri = new Uri(jobContext.Variables.System_TaskDefinitionsUri); } else if (!string.IsNullOrEmpty(jobContext.Variables.System_TFCollectionUrl)) { taskServerUri = new Uri(jobContext.Variables.System_TFCollectionUrl); } var taskServerCredential = ApiUtil.GetVssCredential(systemConnection); if (taskServerUri != null) { Trace.Info($"Creating task server with {taskServerUri}"); await taskServer.ConnectAsync(ApiUtil.CreateConnection(taskServerUri, taskServerCredential)); } if (taskServerUri == null || !await taskServer.TaskDefinitionEndpointExist()) { Trace.Info($"Can't determine task download url from JobMessage or the endpoint doesn't exist."); var configStore = HostContext.GetService <IConfigurationStore>(); taskServerUri = new Uri(configStore.GetSettings().ServerUrl); Trace.Info($"Recreate task server with configuration server url: {taskServerUri}"); await taskServer.ConnectAsync(ApiUtil.CreateConnection(taskServerUri, taskServerCredential)); } // Expand the endpoint data values. foreach (ServiceEndpoint endpoint in jobContext.Endpoints) { jobContext.Variables.ExpandValues(target: endpoint.Data); VarUtil.ExpandEnvironmentVariables(HostContext, target: endpoint.Data); } // Get the job extension. Trace.Info("Getting job extension."); var hostType = jobContext.Variables.System_HostType; var extensionManager = HostContext.GetService <IExtensionManager>(); // We should always have one job extension IJobExtension jobExtension = (extensionManager.GetExtensions <IJobExtension>() ?? new List <IJobExtension>()) .Where(x => x.HostType.HasFlag(hostType)) .FirstOrDefault(); ArgUtil.NotNull(jobExtension, nameof(jobExtension)); List <IStep> jobSteps = new List <IStep>(); try { Trace.Info("Initialize job. Getting all job steps."); var initializeResult = await jobExtension.InitializeJob(jobContext, message); jobSteps.AddRange(initializeResult.PreJobSteps); jobSteps.AddRange(initializeResult.JobSteps); jobSteps.AddRange(initializeResult.PostJobStep); } catch (OperationCanceledException ex) when(jobContext.CancellationToken.IsCancellationRequested) { // set the job to canceled // don't log error issue to job ExecutionContext, since server owns the job level issue Trace.Error($"Job is canceled during initialize."); Trace.Error($"Caught exception: {ex}"); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Canceled)); } catch (Exception ex) { // set the job to failed. // don't log error issue to job ExecutionContext, since server owns the job level issue Trace.Error($"Job initialize failed."); Trace.Error($"Caught exception from {nameof(jobExtension.InitializeJob)}: {ex}"); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } // trace out all steps Trace.Info($"Total job steps: {jobSteps.Count}."); Trace.Verbose($"Job steps: '{string.Join(", ", jobSteps.Select(x => x.DisplayName))}'"); bool processCleanup = jobContext.Variables.GetBoolean("process.clean") ?? true; HashSet <string> existingProcesses = new HashSet <string>(StringComparer.OrdinalIgnoreCase); string processLookupId = null; if (processCleanup) { processLookupId = $"vsts_{Guid.NewGuid()}"; // Set the VSTS_PROCESS_LOOKUP_ID env variable. jobContext.SetVariable(Constants.ProcessLookupId, processLookupId, false, false); // Take a snapshot of current running processes Dictionary <int, Process> processes = SnapshotProcesses(); foreach (var proc in processes) { // Pid_ProcessName existingProcesses.Add($"{proc.Key}_{proc.Value.ProcessName}"); } } // Run all job steps Trace.Info("Run all job steps."); var stepsRunner = HostContext.GetService <IStepsRunner>(); try { await stepsRunner.RunAsync(jobContext, jobSteps); } catch (Exception ex) { // StepRunner should never throw exception out. // End up here mean there is a bug in StepRunner // Log the error and fail the job. Trace.Error($"Caught exception from job steps {nameof(StepsRunner)}: {ex}"); jobContext.Error(ex); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } finally { if (processCleanup) { // Only check environment variable for any process that doesn't run before we invoke our process. Dictionary <int, Process> currentProcesses = SnapshotProcesses(); foreach (var proc in currentProcesses) { if (existingProcesses.Contains($"{proc.Key}_{proc.Value.ProcessName}")) { Trace.Verbose($"Skip existing process. PID: {proc.Key} ({proc.Value.ProcessName})"); } else { Trace.Info($"Inspecting process environment variables. PID: {proc.Key} ({proc.Value.ProcessName})"); Dictionary <string, string> env = new Dictionary <string, string>(); try { env = proc.Value.GetEnvironmentVariables(); foreach (var e in env) { Trace.Verbose($"PID:{proc.Key} ({e.Key}={e.Value})"); } } catch (Exception ex) { Trace.Verbose("Ignore any exception during read process environment variables."); Trace.Verbose(ex.ToString()); } if (env.TryGetValue(Constants.ProcessLookupId, out string lookupId) && lookupId.Equals(processLookupId, StringComparison.OrdinalIgnoreCase)) { Trace.Info($"Terminate orphan process: pid ({proc.Key}) ({proc.Value.ProcessName})"); try { proc.Value.Kill(); } catch (Exception ex) { Trace.Error("Catch exception during orphan process cleanup."); Trace.Error(ex); } } } } } } Trace.Info($"Job result after all job steps finish: {jobContext.Result ?? TaskResult.Succeeded}"); if (jobContext.Variables.GetBoolean(Constants.Variables.Agent.Diagnostic) ?? false) { Trace.Info("Support log upload starting."); IDiagnosticLogManager diagnosticLogManager = HostContext.GetService <IDiagnosticLogManager>(); try { await diagnosticLogManager.UploadDiagnosticLogsAsync(executionContext : jobContext, message : message, jobStartTimeUtc : jobStartTimeUtc); Trace.Info("Support log upload complete."); } catch (Exception ex) { // Log the error but make sure we continue gracefully. Trace.Info("Error uploading support logs."); Trace.Error(ex); } } Trace.Info("Completing the job execution context."); return(await CompleteJobAsync(jobServer, jobContext, message)); } finally { if (agentShutdownRegistration != null) { agentShutdownRegistration.Value.Dispose(); agentShutdownRegistration = null; } await ShutdownQueue(throwOnFailure : false); } }
public async Task <int> ExecuteCommand(CommandSettings command) { ArgUtil.NotNull(command, nameof(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 UpdateAgentSetting(AgentSettings settings) { // No implementation required }
public async Task ConfigureAsync(CommandSettings command) { ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode)); Trace.Info(nameof(ConfigureAsync)); if (IsConfigured()) { throw new InvalidOperationException(StringUtil.Loc("AlreadyConfiguredError")); } // Populate proxy setting from commandline args var vstsProxy = HostContext.GetService <IVstsAgentWebProxy>(); bool saveProxySetting = false; string proxyUrl = command.GetProxyUrl(); if (!string.IsNullOrEmpty(proxyUrl)) { if (!Uri.IsWellFormedUriString(proxyUrl, UriKind.Absolute)) { throw new ArgumentOutOfRangeException(nameof(proxyUrl)); } Trace.Info("Reset proxy base on commandline args."); string proxyUserName = command.GetProxyUserName(); string proxyPassword = command.GetProxyPassword(); (vstsProxy as VstsAgentWebProxy).SetupProxy(proxyUrl, proxyUserName, proxyPassword); saveProxySetting = true; } // Populate cert setting from commandline args var agentCertManager = HostContext.GetService <IAgentCertificateManager>(); bool saveCertSetting = false; bool skipCertValidation = command.GetSkipCertificateValidation(); string caCert = command.GetCACertificate(); string clientCert = command.GetClientCertificate(); string clientCertKey = command.GetClientCertificatePrivateKey(); string clientCertArchive = command.GetClientCertificateArchrive(); string clientCertPassword = command.GetClientCertificatePassword(); // We require all Certificate files are under agent root. // So we can set ACL correctly when configure as service if (!string.IsNullOrEmpty(caCert)) { caCert = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), caCert); ArgUtil.File(caCert, nameof(caCert)); } if (!string.IsNullOrEmpty(clientCert) && !string.IsNullOrEmpty(clientCertKey) && !string.IsNullOrEmpty(clientCertArchive)) { // Ensure all client cert pieces are there. clientCert = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), clientCert); clientCertKey = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), clientCertKey); clientCertArchive = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), clientCertArchive); ArgUtil.File(clientCert, nameof(clientCert)); ArgUtil.File(clientCertKey, nameof(clientCertKey)); ArgUtil.File(clientCertArchive, nameof(clientCertArchive)); } else if (!string.IsNullOrEmpty(clientCert) || !string.IsNullOrEmpty(clientCertKey) || !string.IsNullOrEmpty(clientCertArchive)) { // Print out which args are missing. ArgUtil.NotNullOrEmpty(Constants.Agent.CommandLine.Args.SslClientCert, Constants.Agent.CommandLine.Args.SslClientCert); ArgUtil.NotNullOrEmpty(Constants.Agent.CommandLine.Args.SslClientCertKey, Constants.Agent.CommandLine.Args.SslClientCertKey); ArgUtil.NotNullOrEmpty(Constants.Agent.CommandLine.Args.SslClientCertArchive, Constants.Agent.CommandLine.Args.SslClientCertArchive); } if (skipCertValidation || !string.IsNullOrEmpty(caCert) || !string.IsNullOrEmpty(clientCert)) { Trace.Info("Reset agent cert setting base on commandline args."); (agentCertManager as AgentCertificateManager).SetupCertificate(skipCertValidation, caCert, clientCert, clientCertKey, clientCertArchive, clientCertPassword); saveCertSetting = true; } AgentSettings agentSettings = new AgentSettings(); // TEE EULA agentSettings.AcceptTeeEula = false; switch (Constants.Agent.Platform) { case Constants.OSPlatform.OSX: case Constants.OSPlatform.Linux: // Write the section header. WriteSection(StringUtil.Loc("EulasSectionHeader")); // Verify the EULA exists on disk in the expected location. string eulaFile = Path.Combine(IOUtil.GetExternalsPath(), Constants.Path.TeeDirectory, "license.html"); ArgUtil.File(eulaFile, nameof(eulaFile)); // Write elaborate verbiage about the TEE EULA. _term.WriteLine(StringUtil.Loc("TeeEula", eulaFile)); _term.WriteLine(); // Prompt to acccept the TEE EULA. agentSettings.AcceptTeeEula = command.GetAcceptTeeEula(); break; case Constants.OSPlatform.Windows: // Warn and continue if .NET 4.6 is not installed. var netFrameworkUtil = HostContext.GetService <INetFrameworkUtil>(); if (!netFrameworkUtil.Test(new Version(4, 6))) { WriteSection(StringUtil.Loc("PrerequisitesSectionHeader")); // Section header. _term.WriteLine(StringUtil.Loc("MinimumNetFrameworkTfvc")); // Warning. } break; default: throw new NotSupportedException(); } // Create the configuration provider as per agent type. string agentType; if (command.DeploymentGroup) { agentType = Constants.Agent.AgentConfigurationProvider.DeploymentAgentConfiguration; } else if (command.DeploymentPool) { agentType = Constants.Agent.AgentConfigurationProvider.SharedDeploymentAgentConfiguration; } else { agentType = Constants.Agent.AgentConfigurationProvider.BuildReleasesAgentConfiguration; } var extensionManager = HostContext.GetService <IExtensionManager>(); IConfigurationProvider agentProvider = (extensionManager.GetExtensions <IConfigurationProvider>()) .FirstOrDefault(x => x.ConfigurationProviderType == agentType); ArgUtil.NotNull(agentProvider, agentType); bool isHostedServer = false; // Loop getting url and creds until you can connect ICredentialProvider credProvider = null; VssCredentials creds = null; WriteSection(StringUtil.Loc("ConnectSectionHeader")); while (true) { // Get the URL agentProvider.GetServerUrl(agentSettings, command); // Get the credentials credProvider = GetCredentialProvider(command, agentSettings.ServerUrl); creds = credProvider.GetVssCredentials(HostContext); Trace.Info("cred retrieved"); try { // Determine the service deployment type based on connection data. (Hosted/OnPremises) isHostedServer = await IsHostedServer(agentSettings.ServerUrl, creds); // Get the collection name for deployment group agentProvider.GetCollectionName(agentSettings, command, isHostedServer); // Validate can connect. await agentProvider.TestConnectionAsync(agentSettings, creds, isHostedServer); Trace.Info("Test Connection complete."); break; } catch (Exception e) when(!command.Unattended) { _term.WriteError(e); _term.WriteError(StringUtil.Loc("FailedToConnect")); } } _agentServer = HostContext.GetService <IAgentServer>(); // We want to use the native CSP of the platform for storage, so we use the RSACSP directly RSAParameters publicKey; var keyManager = HostContext.GetService <IRSAKeyManager>(); using (var rsa = keyManager.CreateKey()) { publicKey = rsa.ExportParameters(false); } // Loop getting agent name and pool name WriteSection(StringUtil.Loc("RegisterAgentSectionHeader")); while (true) { try { await agentProvider.GetPoolId(agentSettings, command); break; } catch (Exception e) when(!command.Unattended) { _term.WriteError(e); _term.WriteError(agentProvider.GetFailedToFindPoolErrorString()); } } TaskAgent agent; while (true) { agentSettings.AgentName = command.GetAgentName(); // Get the system capabilities. // TODO: Hook up to ctrl+c cancellation token. _term.WriteLine(StringUtil.Loc("ScanToolCapabilities")); Dictionary <string, string> systemCapabilities = await HostContext.GetService <ICapabilitiesManager>().GetCapabilitiesAsync(agentSettings, CancellationToken.None); _term.WriteLine(StringUtil.Loc("ConnectToServer")); agent = await agentProvider.GetAgentAsync(agentSettings); if (agent != null) { if (command.GetReplace()) { // Update existing agent with new PublicKey, agent version and SystemCapabilities. agent = UpdateExistingAgent(agent, publicKey, systemCapabilities); try { agent = await agentProvider.UpdateAgentAsync(agentSettings, agent, command); _term.WriteLine(StringUtil.Loc("AgentReplaced")); break; } catch (Exception e) when(!command.Unattended) { _term.WriteError(e); _term.WriteError(StringUtil.Loc("FailedToReplaceAgent")); } } else if (command.Unattended) { // if not replace and it is unattended config. agentProvider.ThrowTaskAgentExistException(agentSettings); } } else { // Create a new agent. agent = CreateNewAgent(agentSettings.AgentName, publicKey, systemCapabilities); try { agent = await agentProvider.AddAgentAsync(agentSettings, agent, command); _term.WriteLine(StringUtil.Loc("AgentAddedSuccessfully")); break; } catch (Exception e) when(!command.Unattended) { _term.WriteError(e); _term.WriteError(StringUtil.Loc("AddAgentFailed")); } } } // Add Agent Id to settings agentSettings.AgentId = agent.Id; // respect the serverUrl resolve by server. // in case of agent configured using collection url instead of account url. string agentServerUrl; if (agent.Properties.TryGetValidatedValue <string>("ServerUrl", out agentServerUrl) && !string.IsNullOrEmpty(agentServerUrl)) { Trace.Info($"Agent server url resolve by server: '{agentServerUrl}'."); // we need make sure the Schema/Host/Port component of the url remain the same. UriBuilder inputServerUrl = new UriBuilder(agentSettings.ServerUrl); UriBuilder serverReturnedServerUrl = new UriBuilder(agentServerUrl); if (Uri.Compare(inputServerUrl.Uri, serverReturnedServerUrl.Uri, UriComponents.SchemeAndServer, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) != 0) { inputServerUrl.Path = serverReturnedServerUrl.Path; Trace.Info($"Replace server returned url's scheme://host:port component with user input server url's scheme://host:port: '{inputServerUrl.Uri.AbsoluteUri}'."); agentSettings.ServerUrl = inputServerUrl.Uri.AbsoluteUri; } else { agentSettings.ServerUrl = agentServerUrl; } } // See if the server supports our OAuth key exchange for credentials if (agent.Authorization != null && agent.Authorization.ClientId != Guid.Empty && agent.Authorization.AuthorizationUrl != null) { // We use authorizationUrl as the oauth endpoint url by default. // For TFS, we need make sure the Schema/Host/Port component of the oauth endpoint url also match configuration url. (Incase of customer's agent configure URL and TFS server public URL are different) // Which means, we will keep use the original authorizationUrl in the VssOAuthJwtBearerClientCredential (authorizationUrl is the audience), // But might have different Url in VssOAuthCredential (connection url) // We can't do this for VSTS, since its SPS/TFS urls are different. UriBuilder configServerUrl = new UriBuilder(agentSettings.ServerUrl); UriBuilder oauthEndpointUrlBuilder = new UriBuilder(agent.Authorization.AuthorizationUrl); if (!isHostedServer && Uri.Compare(configServerUrl.Uri, oauthEndpointUrlBuilder.Uri, UriComponents.SchemeAndServer, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) != 0) { oauthEndpointUrlBuilder.Scheme = configServerUrl.Scheme; oauthEndpointUrlBuilder.Host = configServerUrl.Host; oauthEndpointUrlBuilder.Port = configServerUrl.Port; Trace.Info($"Set oauth endpoint url's scheme://host:port component to match agent configure url's scheme://host:port: '{oauthEndpointUrlBuilder.Uri.AbsoluteUri}'."); } var credentialData = new CredentialData { Scheme = Constants.Configuration.OAuth, Data = { { "clientId", agent.Authorization.ClientId.ToString("D") }, { "authorizationUrl", agent.Authorization.AuthorizationUrl.AbsoluteUri }, { "oauthEndpointUrl", oauthEndpointUrlBuilder.Uri.AbsoluteUri }, }, }; // Save the negotiated OAuth credential data _store.SaveCredential(credentialData); } else { switch (Constants.Agent.Platform) { case Constants.OSPlatform.OSX: case Constants.OSPlatform.Linux: // Save the provided admin cred for compat with previous agent. _store.SaveCredential(credProvider.CredentialData); break; case Constants.OSPlatform.Windows: // Not supported against TFS 2015. _term.WriteError(StringUtil.Loc("Tfs2015NotSupported")); return; default: throw new NotSupportedException(); } } // Testing agent connection, detect any protential connection issue, like local clock skew that cause OAuth token expired. _term.WriteLine(StringUtil.Loc("TestAgentConnection")); var credMgr = HostContext.GetService <ICredentialManager>(); VssCredentials credential = credMgr.LoadCredentials(); VssConnection conn = ApiUtil.CreateConnection(new Uri(agentSettings.ServerUrl), credential); var agentSvr = HostContext.GetService <IAgentServer>(); try { await agentSvr.ConnectAsync(conn); } catch (VssOAuthTokenRequestException ex) when(ex.Message.Contains("Current server time is")) { // there are two exception messages server send that indicate clock skew. // 1. The bearer token expired on {jwt.ValidTo}. Current server time is {DateTime.UtcNow}. // 2. The bearer token is not valid until {jwt.ValidFrom}. Current server time is {DateTime.UtcNow}. Trace.Error("Catch exception during test agent connection."); Trace.Error(ex); throw new Exception(StringUtil.Loc("LocalClockSkewed")); } // We will Combine() what's stored with root. Defaults to string a relative path agentSettings.WorkFolder = command.GetWork(); // notificationPipeName for Hosted agent provisioner. agentSettings.NotificationPipeName = command.GetNotificationPipeName(); agentSettings.NotificationSocketAddress = command.GetNotificationSocketAddress(); _store.SaveSettings(agentSettings); if (saveProxySetting) { Trace.Info("Save proxy setting to disk."); (vstsProxy as VstsAgentWebProxy).SaveProxySetting(); } if (saveCertSetting) { Trace.Info("Save agent cert setting to disk."); (agentCertManager as AgentCertificateManager).SaveCertificateSetting(); } _term.WriteLine(StringUtil.Loc("SavedSettings", DateTime.UtcNow)); bool saveRuntimeOptions = false; var runtimeOptions = new AgentRuntimeOptions(); #if OS_WINDOWS if (command.GitUseSChannel) { saveRuntimeOptions = true; runtimeOptions.GitUseSecureChannel = true; } #endif if (saveRuntimeOptions) { Trace.Info("Save agent runtime options to disk."); _store.SaveAgentRuntimeOptions(runtimeOptions); } #if OS_WINDOWS // config windows service bool runAsService = command.GetRunAsService(); if (runAsService) { Trace.Info("Configuring to run the agent as service"); var serviceControlManager = HostContext.GetService <IWindowsServiceControlManager>(); serviceControlManager.ConfigureService(agentSettings, command); } // config auto logon else if (command.GetRunAsAutoLogon()) { Trace.Info("Agent is going to run as process setting up the 'AutoLogon' capability for the agent."); var autoLogonConfigManager = HostContext.GetService <IAutoLogonManager>(); await autoLogonConfigManager.ConfigureAsync(command); //Important: The machine may restart if the autologon user is not same as the current user //if you are adding code after this, keep that in mind } #elif OS_LINUX || OS_OSX // generate service config script for OSX and Linux, GenerateScripts() will no-opt on windows. var serviceControlManager = HostContext.GetService <ILinuxServiceControlManager>(); serviceControlManager.GenerateScripts(agentSettings); #endif }
public async Task UnconfigureAsync(CommandSettings command) { ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode)); string currentAction = string.Empty; try { //stop, uninstall service and remove service config file if (_store.IsServiceConfigured()) { currentAction = StringUtil.Loc("UninstallingService"); _term.WriteLine(currentAction); #if OS_WINDOWS var serviceControlManager = HostContext.GetService <IWindowsServiceControlManager>(); serviceControlManager.UnconfigureService(); _term.WriteLine(StringUtil.Loc("Success") + currentAction); #elif OS_LINUX // unconfig system D service first throw new Exception(StringUtil.Loc("UnconfigureServiceDService")); #elif OS_OSX // unconfig osx service first throw new Exception(StringUtil.Loc("UnconfigureOSXService")); #endif } else { #if OS_WINDOWS //running as process, unconfigure autologon if it was configured if (_store.IsAutoLogonConfigured()) { currentAction = StringUtil.Loc("UnconfigAutologon"); _term.WriteLine(currentAction); var autoLogonConfigManager = HostContext.GetService <IAutoLogonManager>(); autoLogonConfigManager.Unconfigure(); _term.WriteLine(StringUtil.Loc("Success") + currentAction); } else { Trace.Info("AutoLogon was not configured on the agent."); } #endif } //delete agent from the server currentAction = StringUtil.Loc("UnregisteringAgent"); _term.WriteLine(currentAction); bool isConfigured = _store.IsConfigured(); bool hasCredentials = _store.HasCredentials(); if (isConfigured && hasCredentials) { AgentSettings settings = _store.GetSettings(); var credentialManager = HostContext.GetService <ICredentialManager>(); // Get the credentials var credProvider = GetCredentialProvider(command, settings.ServerUrl); VssCredentials creds = credProvider.GetVssCredentials(HostContext); Trace.Info("cred retrieved"); bool isDeploymentGroup = (settings.MachineGroupId > 0) || (settings.DeploymentGroupId > 0); Trace.Info("Agent configured for deploymentGroup : {0}", isDeploymentGroup.ToString()); string agentType = isDeploymentGroup ? Constants.Agent.AgentConfigurationProvider.DeploymentAgentConfiguration : Constants.Agent.AgentConfigurationProvider.BuildReleasesAgentConfiguration; var extensionManager = HostContext.GetService <IExtensionManager>(); IConfigurationProvider agentProvider = (extensionManager.GetExtensions <IConfigurationProvider>()).FirstOrDefault(x => x.ConfigurationProviderType == agentType); ArgUtil.NotNull(agentProvider, agentType); // Determine the service deployment type based on connection data. (Hosted/OnPremises) bool isHostedServer = await IsHostedServer(settings.ServerUrl, creds); await agentProvider.TestConnectionAsync(settings, creds, isHostedServer); TaskAgent agent = await agentProvider.GetAgentAsync(settings); if (agent == null) { _term.WriteLine(StringUtil.Loc("Skipping") + currentAction); } else { await agentProvider.DeleteAgentAsync(settings); _term.WriteLine(StringUtil.Loc("Success") + currentAction); } } else { _term.WriteLine(StringUtil.Loc("MissingConfig")); } //delete credential config files currentAction = StringUtil.Loc("DeletingCredentials"); _term.WriteLine(currentAction); if (hasCredentials) { _store.DeleteCredential(); var keyManager = HostContext.GetService <IRSAKeyManager>(); keyManager.DeleteKey(); _term.WriteLine(StringUtil.Loc("Success") + currentAction); } else { _term.WriteLine(StringUtil.Loc("Skipping") + currentAction); } //delete settings config file currentAction = StringUtil.Loc("DeletingSettings"); _term.WriteLine(currentAction); if (isConfigured) { // delete proxy setting (HostContext.GetService <IVstsAgentWebProxy>() as VstsAgentWebProxy).DeleteProxySetting(); // delete agent cert setting (HostContext.GetService <IAgentCertificateManager>() as AgentCertificateManager).DeleteCertificateSetting(); // delete agent runtime option _store.DeleteAgentRuntimeOptions(); _store.DeleteSettings(); _term.WriteLine(StringUtil.Loc("Success") + currentAction); } else { _term.WriteLine(StringUtil.Loc("Skipping") + currentAction); } } catch (Exception) { _term.WriteLine(StringUtil.Loc("Failed") + currentAction); throw; } }
public async Task CanEnsureMachineGroupAgentConfigureOnPremScenario() { using (TestHostContext tc = CreateTestContext()) { Tracing trace = tc.GetTrace(); trace.Info("Creating config manager"); IConfigurationManager configManager = new ConfigurationManager(); configManager.Initialize(tc); var onPremTfsUrl = "http://localtfs:8080/tfs"; trace.Info("Preparing command line arguments for vsts scenario"); var command = new CommandSettings( tc, new[] { "configure", #if !OS_WINDOWS "--acceptteeeula", #endif "--deploymentgroup", "--url", onPremTfsUrl, "--agent", _expectedAgentName, "--collectionname", _expectedCollectionName, "--projectname", _expectedProjectName, "--deploymentgroupname", _expectedMachineGroupName, "--work", _expectedWorkFolder, "--auth", _expectedAuthType, "--token", _expectedToken }); trace.Info("Constructed."); _store.Setup(x => x.IsConfigured()).Returns(false); _configMgrAgentSettings = null; _extnMgr.Setup(x => x.GetExtensions <IConfigurationProvider>()).Returns(GetConfigurationProviderList(tc)); _machineGroupServer.Setup(x => x.GetDeploymentGroupsAsync(It.IsAny <string>(), It.IsAny <string>())).Returns(Task.FromResult(GetDeploymentGroups(3, 7))); trace.Info("Ensuring all the required parameters are available in the command line parameter"); await configManager.ConfigureAsync(command); _store.Setup(x => x.IsConfigured()).Returns(true); trace.Info("Configured, verifying all the parameter value"); var s = configManager.LoadSettings(); Assert.NotNull(s); Assert.True(s.ServerUrl.Equals(onPremTfsUrl)); Assert.True(s.AgentName.Equals(_expectedAgentName)); Assert.True(s.PoolId.Equals(7)); Assert.True(s.WorkFolder.Equals(_expectedWorkFolder)); Assert.True(s.MachineGroupId.Equals(0)); Assert.True(s.DeploymentGroupId.Equals(3)); Assert.Null(s.ProjectName); Assert.True(s.ProjectId.Equals(_expectedProjectId)); // Tags logic should not get trigger _machineGroupServer.Verify(x => x.UpdateDeploymentTargetsAsync(It.IsAny <Guid>(), It.IsAny <int>(), It.IsAny <List <DeploymentMachine> >()), Times.Never); } }
public async Task GetSourceAsync( IExecutionContext executionContext, ServiceEndpoint endpoint, CancellationToken cancellationToken) { Trace.Entering(); // Validate args. ArgUtil.NotNull(executionContext, nameof(executionContext)); ArgUtil.NotNull(endpoint, nameof(endpoint)); #if OS_WINDOWS // Validate .NET Framework 4.6 or higher is installed. var netFrameworkUtil = HostContext.GetService <INetFrameworkUtil>(); if (!netFrameworkUtil.Test(new Version(4, 6))) { throw new Exception(StringUtil.Loc("MinimumNetFramework46")); } #endif // Create the tf command manager. var tf = HostContext.CreateService <ITfsVCCommandManager>(); tf.CancellationToken = cancellationToken; tf.Endpoint = endpoint; tf.ExecutionContext = executionContext; // Setup proxy. var agentProxy = HostContext.GetService <IVstsAgentWebProxy>(); if (!string.IsNullOrEmpty(executionContext.Variables.Agent_ProxyUrl) && !agentProxy.IsBypassed(endpoint.Url)) { executionContext.Debug($"Configure '{tf.FilePath}' to work through proxy server '{executionContext.Variables.Agent_ProxyUrl}'."); tf.SetupProxy(executionContext.Variables.Agent_ProxyUrl, executionContext.Variables.Agent_ProxyUsername, executionContext.Variables.Agent_ProxyPassword); } // Add TF to the PATH. string tfPath = tf.FilePath; ArgUtil.File(tfPath, nameof(tfPath)); var varUtil = HostContext.GetService <IVarUtil>(); executionContext.Output(StringUtil.Loc("Prepending0WithDirectoryContaining1", Constants.PathVariable, Path.GetFileName(tfPath))); varUtil.PrependPath(Path.GetDirectoryName(tfPath)); executionContext.Debug($"{Constants.PathVariable}: '{Environment.GetEnvironmentVariable(Constants.PathVariable)}'"); #if OS_WINDOWS // Set TFVC_BUILDAGENT_POLICYPATH string policyDllPath = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.ServerOM), "Microsoft.TeamFoundation.VersionControl.Controls.dll"); ArgUtil.File(policyDllPath, nameof(policyDllPath)); const string policyPathEnvKey = "TFVC_BUILDAGENT_POLICYPATH"; executionContext.Output(StringUtil.Loc("SetEnvVar", policyPathEnvKey)); Environment.SetEnvironmentVariable(policyPathEnvKey, policyDllPath); #endif // Check if the administrator accepted the license terms of the TEE EULA when configuring the agent. AgentSettings settings = HostContext.GetService <IConfigurationStore>().GetSettings(); if (tf.Features.HasFlag(TfsVCFeatures.Eula) && settings.AcceptTeeEula) { // Check if the "tf eula -accept" command needs to be run for the current user. bool skipEula = false; try { skipEula = tf.TestEulaAccepted(); } catch (Exception ex) { executionContext.Debug("Unexpected exception while testing whether the TEE EULA has been accepted for the current user."); executionContext.Debug(ex.ToString()); } if (!skipEula) { // Run the command "tf eula -accept". try { await tf.EulaAsync(); } catch (Exception ex) { executionContext.Debug(ex.ToString()); executionContext.Warning(ex.Message); } } } // Get the workspaces. executionContext.Output(StringUtil.Loc("QueryingWorkspaceInfo")); ITfsVCWorkspace[] tfWorkspaces = await tf.WorkspacesAsync(); // Determine the workspace name. string buildDirectory = executionContext.Variables.Agent_BuildDirectory; ArgUtil.NotNullOrEmpty(buildDirectory, nameof(buildDirectory)); string workspaceName = $"ws_{Path.GetFileName(buildDirectory)}_{settings.AgentId}"; executionContext.Variables.Set(Constants.Variables.Build.RepoTfvcWorkspace, workspaceName); // Get the definition mappings. DefinitionWorkspaceMapping[] definitionMappings = JsonConvert.DeserializeObject <DefinitionWorkspaceMappings>(endpoint.Data[WellKnownEndpointData.TfvcWorkspaceMapping])?.Mappings; // Determine the sources directory. string sourcesDirectory = GetEndpointData(endpoint, Constants.EndpointData.SourcesDirectory); ArgUtil.NotNullOrEmpty(sourcesDirectory, nameof(sourcesDirectory)); // Attempt to re-use an existing workspace if the command manager supports scorch // or if clean is not specified. ITfsVCWorkspace existingTFWorkspace = null; bool clean = endpoint.Data.ContainsKey(WellKnownEndpointData.Clean) && StringUtil.ConvertToBoolean(endpoint.Data[WellKnownEndpointData.Clean], defaultValue: false); if (tf.Features.HasFlag(TfsVCFeatures.Scorch) || !clean) { existingTFWorkspace = WorkspaceUtil.MatchExactWorkspace( executionContext: executionContext, tfWorkspaces: tfWorkspaces, name: workspaceName, definitionMappings: definitionMappings, sourcesDirectory: sourcesDirectory); if (existingTFWorkspace != null) { if (tf.Features.HasFlag(TfsVCFeatures.GetFromUnmappedRoot)) { // Undo pending changes. ITfsVCStatus tfStatus = await tf.StatusAsync(localPath : sourcesDirectory); if (tfStatus?.HasPendingChanges ?? false) { await tf.UndoAsync(localPath : sourcesDirectory); // Cleanup remaining files/directories from pend adds. tfStatus.AllAdds .OrderByDescending(x => x.LocalItem) // Sort descending so nested items are deleted before their parent is deleted. .ToList() .ForEach(x => { executionContext.Output(StringUtil.Loc("Deleting", x.LocalItem)); IOUtil.Delete(x.LocalItem, cancellationToken); }); } } else { // Perform "undo" for each map. foreach (DefinitionWorkspaceMapping definitionMapping in definitionMappings ?? new DefinitionWorkspaceMapping[0]) { if (definitionMapping.MappingType == DefinitionMappingType.Map) { // Check the status. string localPath = definitionMapping.GetRootedLocalPath(sourcesDirectory); ITfsVCStatus tfStatus = await tf.StatusAsync(localPath : localPath); if (tfStatus?.HasPendingChanges ?? false) { // Undo. await tf.UndoAsync(localPath : localPath); // Cleanup remaining files/directories from pend adds. tfStatus.AllAdds .OrderByDescending(x => x.LocalItem) // Sort descending so nested items are deleted before their parent is deleted. .ToList() .ForEach(x => { executionContext.Output(StringUtil.Loc("Deleting", x.LocalItem)); IOUtil.Delete(x.LocalItem, cancellationToken); }); } } } } // Scorch. if (clean) { // Try to scorch. try { await tf.ScorchAsync(); } catch (ProcessExitCodeException ex) { // Scorch failed. // Warn, drop the folder, and re-clone. executionContext.Warning(ex.Message); existingTFWorkspace = null; } } } } // Create a new workspace. if (existingTFWorkspace == null) { // Remove any conflicting workspaces. await RemoveConflictingWorkspacesAsync( tf : tf, tfWorkspaces : tfWorkspaces, name : workspaceName, directory : sourcesDirectory); // Remove any conflicting workspace from a different computer. // This is primarily a hosted scenario where a registered hosted // agent can land on a different computer each time. tfWorkspaces = await tf.WorkspacesAsync(matchWorkspaceNameOnAnyComputer : true); foreach (ITfsVCWorkspace tfWorkspace in tfWorkspaces ?? new ITfsVCWorkspace[0]) { await tf.WorkspaceDeleteAsync(tfWorkspace); } // Recreate the sources directory. executionContext.Debug($"Deleting: '{sourcesDirectory}'."); IOUtil.DeleteDirectory(sourcesDirectory, cancellationToken); Directory.CreateDirectory(sourcesDirectory); // Create the workspace. await tf.WorkspaceNewAsync(); // Remove the default mapping. if (tf.Features.HasFlag(TfsVCFeatures.DefaultWorkfoldMap)) { await tf.WorkfoldUnmapAsync("$/"); } // Sort the definition mappings. definitionMappings = (definitionMappings ?? new DefinitionWorkspaceMapping[0]) .OrderBy(x => x.NormalizedServerPath?.Length ?? 0) // By server path length. .ToArray() ?? new DefinitionWorkspaceMapping[0]; // Add the definition mappings to the workspace. foreach (DefinitionWorkspaceMapping definitionMapping in definitionMappings) { switch (definitionMapping.MappingType) { case DefinitionMappingType.Cloak: // Add the cloak. await tf.WorkfoldCloakAsync(serverPath : definitionMapping.ServerPath); break; case DefinitionMappingType.Map: // Add the mapping. await tf.WorkfoldMapAsync( serverPath : definitionMapping.ServerPath, localPath : definitionMapping.GetRootedLocalPath(sourcesDirectory)); break; default: throw new NotSupportedException(); } } } if (tf.Features.HasFlag(TfsVCFeatures.GetFromUnmappedRoot)) { // Get. await tf.GetAsync(localPath : sourcesDirectory); } else { // Perform "get" for each map. foreach (DefinitionWorkspaceMapping definitionMapping in definitionMappings ?? new DefinitionWorkspaceMapping[0]) { if (definitionMapping.MappingType == DefinitionMappingType.Map) { await tf.GetAsync(localPath : definitionMapping.GetRootedLocalPath(sourcesDirectory)); } } } // Steps for shelveset/gated. string shelvesetName = GetEndpointData(endpoint, Constants.EndpointData.SourceTfvcShelveset); if (!string.IsNullOrEmpty(shelvesetName)) { // Steps for gated. ITfsVCShelveset tfShelveset = null; string gatedShelvesetName = GetEndpointData(endpoint, Constants.EndpointData.GatedShelvesetName); if (!string.IsNullOrEmpty(gatedShelvesetName)) { // Clean the last-saved-checkin-metadata for existing workspaces. // // A better long term fix is to add a switch to "tf unshelve" that completely overwrites // the last-saved-checkin-metadata, instead of merging associated work items. // // The targeted workaround for now is to create a trivial change and "tf shelve /move", // which will delete the last-saved-checkin-metadata. if (existingTFWorkspace != null) { executionContext.Output("Cleaning last saved checkin metadata."); // Find a local mapped directory. string firstLocalDirectory = (definitionMappings ?? new DefinitionWorkspaceMapping[0]) .Where(x => x.MappingType == DefinitionMappingType.Map) .Select(x => x.GetRootedLocalPath(sourcesDirectory)) .FirstOrDefault(x => Directory.Exists(x)); if (firstLocalDirectory == null) { executionContext.Warning("No mapped folder found. Unable to clean last-saved-checkin-metadata."); } else { // Create a trival change and "tf shelve /move" to clear the // last-saved-checkin-metadata. string cleanName = "__tf_clean_wksp_metadata"; string tempCleanFile = Path.Combine(firstLocalDirectory, cleanName); try { File.WriteAllText(path: tempCleanFile, contents: "clean last-saved-checkin-metadata", encoding: Encoding.UTF8); await tf.AddAsync(tempCleanFile); await tf.ShelveAsync(shelveset : cleanName, commentFile : tempCleanFile, move : true); } catch (Exception ex) { executionContext.Warning($"Unable to clean last-saved-checkin-metadata. {ex.Message}"); try { await tf.UndoAsync(tempCleanFile); } catch (Exception ex2) { executionContext.Warning($"Unable to undo '{tempCleanFile}'. {ex2.Message}"); } } finally { IOUtil.DeleteFile(tempCleanFile); } } } // Get the shelveset metadata. tfShelveset = await tf.ShelvesetsAsync(shelveset : shelvesetName); // The above command throws if the shelveset is not found, // so the following assertion should never fail. ArgUtil.NotNull(tfShelveset, nameof(tfShelveset)); } // Unshelve. await tf.UnshelveAsync(shelveset : shelvesetName); // Ensure we undo pending changes for shelveset build at the end. _undoShelvesetPendingChanges = true; if (!string.IsNullOrEmpty(gatedShelvesetName)) { // Create the comment file for reshelve. StringBuilder comment = new StringBuilder(tfShelveset.Comment ?? string.Empty); string runCi = GetEndpointData(endpoint, Constants.EndpointData.GatedRunCI); bool gatedRunCi = StringUtil.ConvertToBoolean(runCi, true); if (!gatedRunCi) { if (comment.Length > 0) { comment.AppendLine(); } comment.Append(Constants.Build.NoCICheckInComment); } string commentFile = null; try { commentFile = Path.GetTempFileName(); File.WriteAllText(path: commentFile, contents: comment.ToString(), encoding: Encoding.UTF8); // Reshelve. await tf.ShelveAsync(shelveset : gatedShelvesetName, commentFile : commentFile, move : false); } finally { // Cleanup the comment file. if (File.Exists(commentFile)) { File.Delete(commentFile); } } } } // Cleanup proxy settings. if (!string.IsNullOrEmpty(executionContext.Variables.Agent_ProxyUrl) && !agentProxy.IsBypassed(endpoint.Url)) { executionContext.Debug($"Remove proxy setting for '{tf.FilePath}' to work through proxy server '{executionContext.Variables.Agent_ProxyUrl}'."); tf.CleanupProxySetting(); } }
public async Task GetSourceAsync( IExecutionContext executionContext, ServiceEndpoint endpoint, CancellationToken cancellationToken) { Trace.Entering(); // Validate args. ArgUtil.NotNull(executionContext, nameof(executionContext)); ArgUtil.NotNull(endpoint, nameof(endpoint)); // Create the tf command manager. var tf = HostContext.CreateService <ITfsVCCommandManager>(); tf.CancellationToken = cancellationToken; tf.Endpoint = endpoint; tf.ExecutionContext = executionContext; // Add TF to the PATH. string tfPath = tf.FilePath; ArgUtil.File(tfPath, nameof(tfPath)); var varUtil = HostContext.GetService <IVarUtil>(); executionContext.Output(StringUtil.Loc("Prepending0WithDirectoryContaining1", Constants.PathVariable, Path.GetFileName(tfPath))); varUtil.PrependPath(Path.GetDirectoryName(tfPath)); executionContext.Debug($"{Constants.PathVariable}: '{Environment.GetEnvironmentVariable(Constants.PathVariable)}'"); // Check if the administrator accepted the license terms of the TEE EULA when configuring the agent. AgentSettings settings = HostContext.GetService <IConfigurationStore>().GetSettings(); if (tf.Features.HasFlag(TfsVCFeatures.Eula) && settings.AcceptTeeEula) { // Check if the "tf eula -accept" command needs to be run for the current user. bool skipEula = false; try { skipEula = tf.TestEulaAccepted(); } catch (Exception ex) { executionContext.Debug("Unexpected exception while testing whether the TEE EULA has been accepted for the current user."); executionContext.Debug(ex.ToString()); } if (!skipEula) { // Run the command "tf eula -accept". try { await tf.EulaAsync(); } catch (Exception ex) { executionContext.Debug(ex.ToString()); executionContext.Warning(ex.Message); } } } // Get the workspaces. executionContext.Output(StringUtil.Loc("QueryingWorkspaceInfo")); ITfsVCWorkspace[] tfWorkspaces = await tf.WorkspacesAsync(); // Determine the workspace name. string buildDirectory = executionContext.Variables.Agent_BuildDirectory; ArgUtil.NotNullOrEmpty(buildDirectory, nameof(buildDirectory)); string workspaceName = $"ws_{Path.GetFileName(buildDirectory)}_{settings.AgentId}"; executionContext.Variables.Set(Constants.Variables.Build.RepoTfvcWorkspace, workspaceName); // Get the definition mappings. DefinitionWorkspaceMapping[] definitionMappings = JsonConvert.DeserializeObject <DefinitionWorkspaceMappings>(endpoint.Data[WellKnownEndpointData.TfvcWorkspaceMapping])?.Mappings; // Determine the sources directory. string sourcesDirectory = GetEndpointData(endpoint, Constants.EndpointData.SourcesDirectory); ArgUtil.NotNullOrEmpty(sourcesDirectory, nameof(sourcesDirectory)); // Attempt to re-use an existing workspace if the command manager supports scorch // or if clean is not specified. ITfsVCWorkspace existingTFWorkspace = null; bool clean = endpoint.Data.ContainsKey(WellKnownEndpointData.Clean) && StringUtil.ConvertToBoolean(endpoint.Data[WellKnownEndpointData.Clean], defaultValue: false); if (tf.Features.HasFlag(TfsVCFeatures.Scorch) || !clean) { existingTFWorkspace = WorkspaceUtil.MatchExactWorkspace( executionContext: executionContext, tfWorkspaces: tfWorkspaces, name: workspaceName, definitionMappings: definitionMappings, sourcesDirectory: sourcesDirectory); if (existingTFWorkspace != null) { if (tf.Features.HasFlag(TfsVCFeatures.GetFromUnmappedRoot)) { // Undo pending changes. ITfsVCStatus tfStatus = await tf.StatusAsync(localPath : sourcesDirectory); if (tfStatus?.HasPendingChanges ?? false) { await tf.UndoAsync(localPath : sourcesDirectory); // Cleanup remaining files/directories from pend adds. tfStatus.AllAdds .OrderByDescending(x => x.LocalItem) // Sort descending so nested items are deleted before their parent is deleted. .ToList() .ForEach(x => { executionContext.Output(StringUtil.Loc("Deleting", x.LocalItem)); IOUtil.Delete(x.LocalItem, cancellationToken); }); } } else { // Perform "undo" for each map. foreach (DefinitionWorkspaceMapping definitionMapping in definitionMappings ?? new DefinitionWorkspaceMapping[0]) { if (definitionMapping.MappingType == DefinitionMappingType.Map) { // Check the status. string localPath = definitionMapping.GetRootedLocalPath(sourcesDirectory); ITfsVCStatus tfStatus = await tf.StatusAsync(localPath : localPath); if (tfStatus?.HasPendingChanges ?? false) { // Undo. await tf.UndoAsync(localPath : localPath); // Cleanup remaining files/directories from pend adds. tfStatus.AllAdds .OrderByDescending(x => x.LocalItem) // Sort descending so nested items are deleted before their parent is deleted. .ToList() .ForEach(x => { executionContext.Output(StringUtil.Loc("Deleting", x.LocalItem)); IOUtil.Delete(x.LocalItem, cancellationToken); }); } } } } // Scorch. if (clean) { // Try to scorch. try { await tf.ScorchAsync(); } catch (ProcessExitCodeException ex) { // Scorch failed. // Warn, drop the folder, and re-clone. executionContext.Warning(ex.Message); existingTFWorkspace = null; } } } } // Create a new workspace. if (existingTFWorkspace == null) { // Remove any conflicting workspaces. await RemoveConflictingWorkspacesAsync( tf : tf, tfWorkspaces : tfWorkspaces, name : workspaceName, directory : sourcesDirectory); // Remove any conflicting workspace from a different computer. // This is primarily a hosted scenario where a registered hosted // agent can land on a different computer each time. tfWorkspaces = await tf.WorkspacesAsync(matchWorkspaceNameOnAnyComputer : true); foreach (ITfsVCWorkspace tfWorkspace in tfWorkspaces ?? new ITfsVCWorkspace[0]) { await tf.WorkspaceDeleteAsync(tfWorkspace); } // Recreate the sources directory. executionContext.Debug($"Deleting: '{sourcesDirectory}'."); IOUtil.DeleteDirectory(sourcesDirectory, cancellationToken); Directory.CreateDirectory(sourcesDirectory); // Create the workspace. await tf.WorkspaceNewAsync(); // Remove the default mapping. if (tf.Features.HasFlag(TfsVCFeatures.DefaultWorkfoldMap)) { await tf.WorkfoldUnmapAsync("$/"); } // Sort the definition mappings. definitionMappings = (definitionMappings ?? new DefinitionWorkspaceMapping[0]) .OrderBy(x => x.NormalizedServerPath?.Length ?? 0) // By server path length. .ToArray() ?? new DefinitionWorkspaceMapping[0]; // Add the definition mappings to the workspace. foreach (DefinitionWorkspaceMapping definitionMapping in definitionMappings) { switch (definitionMapping.MappingType) { case DefinitionMappingType.Cloak: // Add the cloak. await tf.WorkfoldCloakAsync(serverPath : definitionMapping.ServerPath); break; case DefinitionMappingType.Map: // Add the mapping. await tf.WorkfoldMapAsync( serverPath : definitionMapping.ServerPath, localPath : definitionMapping.GetRootedLocalPath(sourcesDirectory)); break; default: throw new NotSupportedException(); } } } if (tf.Features.HasFlag(TfsVCFeatures.GetFromUnmappedRoot)) { // Get. await tf.GetAsync(localPath : sourcesDirectory); } else { // Perform "get" for each map. foreach (DefinitionWorkspaceMapping definitionMapping in definitionMappings ?? new DefinitionWorkspaceMapping[0]) { if (definitionMapping.MappingType == DefinitionMappingType.Map) { await tf.GetAsync(localPath : definitionMapping.GetRootedLocalPath(sourcesDirectory)); } } } string shelvesetName = GetEndpointData(endpoint, Constants.EndpointData.SourceTfvcShelveset); if (!string.IsNullOrEmpty(shelvesetName)) { // Get the shelveset details. ITfsVCShelveset tfShelveset = null; string gatedShelvesetName = GetEndpointData(endpoint, Constants.EndpointData.GatedShelvesetName); if (!string.IsNullOrEmpty(gatedShelvesetName)) { tfShelveset = await tf.ShelvesetsAsync(shelveset : shelvesetName); // The command throws if the shelveset is not found. // This assertion should never fail. ArgUtil.NotNull(tfShelveset, nameof(tfShelveset)); } // Unshelve. await tf.UnshelveAsync(shelveset : shelvesetName); if (!string.IsNullOrEmpty(gatedShelvesetName)) { // Create the comment file for reshelve. StringBuilder comment = new StringBuilder(tfShelveset.Comment ?? string.Empty); string runCi = GetEndpointData(endpoint, Constants.EndpointData.GatedRunCI); bool gatedRunCi = StringUtil.ConvertToBoolean(runCi, true); if (!gatedRunCi) { if (comment.Length > 0) { comment.AppendLine(); } comment.Append(Constants.Build.NoCICheckInComment); } string commentFile = null; try { commentFile = Path.GetTempFileName(); File.WriteAllText(path: commentFile, contents: comment.ToString(), encoding: Encoding.UTF8); // Reshelve. await tf.ShelveAsync(shelveset : gatedShelvesetName, commentFile : commentFile); } finally { // Cleanup the comment file. if (File.Exists(commentFile)) { File.Delete(commentFile); } } } } }
public async Task CanEnsureMachineGroupAgentConfigureVSTSScenarioWithTags() { Guid receivedProjectId = Guid.Empty; string expectedProcessedTags = string.Empty; string tags = "Tag3, ,, Tag4 , , , Tag1, , tag3 "; string expectedTags = "Tag3,Tag4,Tag1"; int receivedMachineId = -1; int expectedDeploymentGroupId = 7; int receivedDeploymentGroupId = -1; _machineGroupServer.Setup(x => x.UpdateDeploymentTargetsAsync(It.IsAny <Guid>(), It.IsAny <int>(), It.IsAny <List <DeploymentMachine> >())).Callback((Guid project, int deploymentGroupId, List <DeploymentMachine> deploymentMachine) => { receivedProjectId = project; expectedProcessedTags = string.Join(",", deploymentMachine.FirstOrDefault().Tags.ToArray()); receivedMachineId = deploymentMachine.FirstOrDefault().Id; receivedDeploymentGroupId = deploymentGroupId; } ); 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 for vsts scenario"); var command = new CommandSettings( tc, new[] { "configure", #if !OS_WINDOWS "--acceptteeeula", #endif "--machinegroup", "--adddeploymentgrouptags", "--url", _expectedVSTSServerUrl, "--agent", _expectedAgentName, "--projectname", _expectedProjectName, "--deploymentgroupname", _expectedMachineGroupName, "--work", _expectedWorkFolder, "--auth", _expectedAuthType, "--token", _expectedToken, "--deploymentgrouptags", tags }); trace.Info("Constructed."); _store.Setup(x => x.IsConfigured()).Returns(false); _configMgrAgentSettings = null; _extnMgr.Setup(x => x.GetExtensions <IConfigurationProvider>()).Returns(GetConfigurationProviderList(tc)); _machineGroupServer.Setup(x => x.GetDeploymentGroupsAsync(It.IsAny <string>(), It.IsAny <string>())).Returns(Task.FromResult(GetDeploymentGroups(expectedDeploymentGroupId, 3))); trace.Info("Ensuring all the required parameters are available in the command line parameter"); await configManager.ConfigureAsync(command); _store.Setup(x => x.IsConfigured()).Returns(true); trace.Info("Configured, verifying all the parameter value"); var s = configManager.LoadSettings(); Assert.NotNull(s); Assert.True(s.ServerUrl.Equals(_expectedVSTSServerUrl, StringComparison.CurrentCultureIgnoreCase)); Assert.True(s.AgentName.Equals(_expectedAgentName)); Assert.True(s.PoolId.Equals(3)); Assert.True(s.DeploymentGroupId.Equals(7)); Assert.True(s.WorkFolder.Equals(_expectedWorkFolder)); Assert.True(s.MachineGroupId.Equals(0)); Assert.Null(s.ProjectName); Assert.True(s.ProjectId.Equals(_expectedProjectId)); Assert.True(receivedProjectId.Equals(new Guid(_expectedProjectId)), "UpdateDeploymentMachinesGroupAsync should get call with correct project name"); Assert.True(expectedTags.Equals(expectedProcessedTags), "Before applying the tags, should get processed ( Trim, Remove duplicate)"); Assert.True(receivedMachineId.Equals(_expectedDeploymentMachineId), "UpdateDeploymentMachinesGroupAsync should get call with correct machine id"); Assert.True(receivedDeploymentGroupId.Equals(expectedDeploymentGroupId), "UpdateDeploymentMachinesGroupAsync should get call with correct deployment group id"); // Tags logic should get trigger _machineGroupServer.Verify(x => x.UpdateDeploymentTargetsAsync(It.IsAny <Guid>(), It.IsAny <int>(), It.IsAny <List <DeploymentMachine> >()), Times.Once); } }
public void ThrowTaskAgentExistException(AgentSettings agentSettings) { throw new TaskAgentExistsException(StringUtil.Loc("DeploymentMachineWithSameNameAlreadyExistInDeploymentGroup", agentSettings.DeploymentGroupId, agentSettings.AgentName)); }
public async Task <TaskResult> RunAsync(AgentJobRequestMessage message, CancellationToken jobRequestCancellationToken) { // Validate parameters. Trace.Entering(); ArgUtil.NotNull(message, nameof(message)); ArgUtil.NotNull(message.Environment, nameof(message.Environment)); ArgUtil.NotNull(message.Environment.Variables, nameof(message.Environment.Variables)); ArgUtil.NotNull(message.Tasks, nameof(message.Tasks)); Trace.Info("Job ID {0}", message.JobId); // Agent.RunMode RunMode runMode; if (message.Environment.Variables.ContainsKey(Constants.Variables.Agent.RunMode) && Enum.TryParse(message.Environment.Variables[Constants.Variables.Agent.RunMode], ignoreCase: true, result: out runMode) && runMode == RunMode.Local) { HostContext.RunMode = runMode; } // System.AccessToken if (message.Environment.Variables.ContainsKey(Constants.Variables.System.EnableAccessToken) && StringUtil.ConvertToBoolean(message.Environment.Variables[Constants.Variables.System.EnableAccessToken])) { // TODO: get access token use Util Method message.Environment.Variables[Constants.Variables.System.AccessToken] = message.Environment.SystemConnection.Authorization.Parameters["AccessToken"]; } // Make sure SystemConnection Url and Endpoint Url match Config Url base ReplaceConfigUriBaseInJobRequestMessage(message); // Setup the job server and job server queue. var jobServer = HostContext.GetService <IJobServer>(); VssCredentials jobServerCredential = ApiUtil.GetVssCredential(message.Environment.SystemConnection); Uri jobServerUrl = message.Environment.SystemConnection.Url; Trace.Info($"Creating job server with URL: {jobServerUrl}"); // jobServerQueue is the throttling reporter. _jobServerQueue = HostContext.GetService <IJobServerQueue>(); VssConnection jobConnection = ApiUtil.CreateConnection(jobServerUrl, jobServerCredential, new DelegatingHandler[] { new ThrottlingReportHandler(_jobServerQueue) }); await jobServer.ConnectAsync(jobConnection); _jobServerQueue.Start(message); IExecutionContext jobContext = null; CancellationTokenRegistration?agentShutdownRegistration = null; try { // Create the job execution context. jobContext = HostContext.CreateService <IExecutionContext>(); jobContext.InitializeJob(message, jobRequestCancellationToken); Trace.Info("Starting the job execution context."); jobContext.Start(); jobContext.Section(StringUtil.Loc("StepStarting", message.JobName)); agentShutdownRegistration = HostContext.AgentShutdownToken.Register(() => { // log an issue, then agent get shutdown by Ctrl-C or Ctrl-Break. // the server will use Ctrl-Break to tells the agent that operating system is shutting down. string errorMessage; switch (HostContext.AgentShutdownReason) { case ShutdownReason.UserCancelled: errorMessage = StringUtil.Loc("UserShutdownAgent"); break; case ShutdownReason.OperatingSystemShutdown: errorMessage = StringUtil.Loc("OperatingSystemShutdown", Environment.MachineName); break; default: throw new ArgumentException(HostContext.AgentShutdownReason.ToString(), nameof(HostContext.AgentShutdownReason)); } jobContext.AddIssue(new Issue() { Type = IssueType.Error, Message = errorMessage }); }); // Set agent version variable. jobContext.Variables.Set(Constants.Variables.Agent.Version, Constants.Agent.Version); jobContext.Output(StringUtil.Loc("AgentVersion", Constants.Agent.Version)); // Print proxy setting information for better diagnostic experience var agentWebProxy = HostContext.GetService <IVstsAgentWebProxy>(); if (!string.IsNullOrEmpty(agentWebProxy.ProxyAddress)) { jobContext.Output(StringUtil.Loc("AgentRunningBehindProxy", agentWebProxy.ProxyAddress)); } // Validate directory permissions. string workDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); Trace.Info($"Validating directory permissions for: '{workDirectory}'"); try { Directory.CreateDirectory(workDirectory); IOUtil.ValidateExecutePermission(workDirectory); } catch (Exception ex) { Trace.Error(ex); jobContext.Error(ex); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } // Set agent variables. AgentSettings settings = HostContext.GetService <IConfigurationStore>().GetSettings(); jobContext.Variables.Set(Constants.Variables.Agent.Id, settings.AgentId.ToString(CultureInfo.InvariantCulture)); jobContext.Variables.Set(Constants.Variables.Agent.HomeDirectory, IOUtil.GetRootPath()); jobContext.Variables.Set(Constants.Variables.Agent.JobName, message.JobName); jobContext.Variables.Set(Constants.Variables.Agent.MachineName, Environment.MachineName); jobContext.Variables.Set(Constants.Variables.Agent.Name, settings.AgentName); jobContext.Variables.Set(Constants.Variables.Agent.OS, VarUtil.OS); jobContext.Variables.Set(Constants.Variables.Agent.RootDirectory, IOUtil.GetWorkPath(HostContext)); #if OS_WINDOWS jobContext.Variables.Set(Constants.Variables.Agent.ServerOMDirectory, Path.Combine(IOUtil.GetExternalsPath(), Constants.Path.ServerOMDirectory)); #endif jobContext.Variables.Set(Constants.Variables.Agent.WorkFolder, IOUtil.GetWorkPath(HostContext)); jobContext.Variables.Set(Constants.Variables.System.WorkFolder, IOUtil.GetWorkPath(HostContext)); if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AGENT_TOOLSDIRECTORY"))) { string toolsDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), Constants.Path.ToolDirectory); Directory.CreateDirectory(toolsDirectory); jobContext.Variables.Set(Constants.Variables.Agent.ToolsDirectory, toolsDirectory); } // Setup TEMP directories _tempDirectoryManager = HostContext.GetService <ITempDirectoryManager>(); _tempDirectoryManager.InitializeTempDirectory(jobContext); // todo: task server can throw. try/catch and fail job gracefully. // prefer task definitions url, then TFS collection url, then TFS account url var taskServer = HostContext.GetService <ITaskServer>(); Uri taskServerUri = null; if (!string.IsNullOrEmpty(jobContext.Variables.System_TaskDefinitionsUri)) { taskServerUri = new Uri(jobContext.Variables.System_TaskDefinitionsUri); } else if (!string.IsNullOrEmpty(jobContext.Variables.System_TFCollectionUrl)) { taskServerUri = new Uri(jobContext.Variables.System_TFCollectionUrl); } var taskServerCredential = ApiUtil.GetVssCredential(message.Environment.SystemConnection); if (taskServerUri != null) { Trace.Info($"Creating task server with {taskServerUri}"); await taskServer.ConnectAsync(ApiUtil.CreateConnection(taskServerUri, taskServerCredential)); } if (taskServerUri == null || !await taskServer.TaskDefinitionEndpointExist()) { Trace.Info($"Can't determine task download url from JobMessage or the endpoint doesn't exist."); var configStore = HostContext.GetService <IConfigurationStore>(); taskServerUri = new Uri(configStore.GetSettings().ServerUrl); Trace.Info($"Recreate task server with configuration server url: {taskServerUri}"); await taskServer.ConnectAsync(ApiUtil.CreateConnection(taskServerUri, taskServerCredential)); } // Expand the endpoint data values. foreach (ServiceEndpoint endpoint in jobContext.Endpoints) { jobContext.Variables.ExpandValues(target: endpoint.Data); VarUtil.ExpandEnvironmentVariables(HostContext, target: endpoint.Data); } // Get the job extension. Trace.Info("Getting job extension."); var hostType = jobContext.Variables.System_HostType; var extensionManager = HostContext.GetService <IExtensionManager>(); // We should always have one job extension IJobExtension jobExtension = (extensionManager.GetExtensions <IJobExtension>() ?? new List <IJobExtension>()) .Where(x => x.HostType.HasFlag(hostType)) .FirstOrDefault(); ArgUtil.NotNull(jobExtension, nameof(jobExtension)); List <IStep> preJobSteps = new List <IStep>(); List <IStep> jobSteps = new List <IStep>(); List <IStep> postJobSteps = new List <IStep>(); try { Trace.Info("Initialize job. Getting all job steps."); var initializeResult = await jobExtension.InitializeJob(jobContext, message); preJobSteps = initializeResult.PreJobSteps; jobSteps = initializeResult.JobSteps; postJobSteps = initializeResult.PostJobStep; } catch (OperationCanceledException ex) when(jobContext.CancellationToken.IsCancellationRequested) { // set the job to canceled // don't log error issue to job ExecutionContext, since server owns the job level issue Trace.Error($"Job is canceled during initialize."); Trace.Error($"Caught exception: {ex}"); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Canceled)); } catch (Exception ex) { // set the job to failed. // don't log error issue to job ExecutionContext, since server owns the job level issue Trace.Error($"Job initialize failed."); Trace.Error($"Caught exception from {nameof(jobExtension.InitializeJob)}: {ex}"); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } // trace out all steps Trace.Info($"Total pre-job steps: {preJobSteps.Count}."); Trace.Verbose($"Pre-job steps: '{string.Join(", ", preJobSteps.Select(x => x.DisplayName))}'"); Trace.Info($"Total job steps: {jobSteps.Count}."); Trace.Verbose($"Job steps: '{string.Join(", ", jobSteps.Select(x => x.DisplayName))}'"); Trace.Info($"Total post-job steps: {postJobSteps.Count}."); Trace.Verbose($"Post-job steps: '{string.Join(", ", postJobSteps.Select(x => x.DisplayName))}'"); // Run all pre job steps // All pre job steps are critical to the job // Stop execution on any step failure or cancelled Trace.Info("Run all pre-job steps."); var stepsRunner = HostContext.GetService <IStepsRunner>(); try { await stepsRunner.RunAsync(jobContext, preJobSteps, JobRunStage.PreJob); } catch (Exception ex) { // StepRunner should never throw exception out. // End up here mean there is a bug in StepRunner // Log the error and fail the job. Trace.Error($"Caught exception from pre-job steps {nameof(StepsRunner)}: {ex}"); jobContext.Error(ex); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } Trace.Info($"Job result after all pre-job steps finish: {jobContext.Result}"); // Base on the Job result after all pre-job steps finish. // Run all job steps only if the job result is still Succeeded or SucceededWithIssues if (jobContext.Result == null || jobContext.Result == TaskResult.Succeeded || jobContext.Result == TaskResult.SucceededWithIssues) { Trace.Info("Run all job steps."); try { await stepsRunner.RunAsync(jobContext, jobSteps, JobRunStage.Main); } catch (Exception ex) { // StepRunner should never throw exception out. // End up here mean there is a bug in StepRunner // Log the error and fail the job. Trace.Error($"Caught exception from job steps {nameof(StepsRunner)}: {ex}"); jobContext.Error(ex); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } } else { Trace.Info("Skip all job steps due to pre-job step failure."); foreach (var step in jobSteps) { step.ExecutionContext.Start(); step.ExecutionContext.Complete(TaskResult.Skipped); } } Trace.Info($"Job result after all job steps finish: {jobContext.Result}"); // Always run all post job steps // step might not run base on it's own condition. Trace.Info("Run all post-job steps."); try { await stepsRunner.RunAsync(jobContext, postJobSteps, JobRunStage.PostJob); } catch (Exception ex) { // StepRunner should never throw exception out. // End up here mean there is a bug in StepRunner // Log the error and fail the job. Trace.Error($"Caught exception from post-job steps {nameof(StepsRunner)}: {ex}"); jobContext.Error(ex); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } Trace.Info($"Job result after all post-job steps finish: {jobContext.Result}"); // Complete the job. Trace.Info("Completing the job execution context."); return(await CompleteJobAsync(jobServer, jobContext, message)); } finally { if (agentShutdownRegistration != null) { agentShutdownRegistration.Value.Dispose(); agentShutdownRegistration = null; } await ShutdownQueue(throwOnFailure : false); } }
public void GetServerUrl(AgentSettings agentSettings, CommandSettings command) { agentSettings.ServerUrl = command.GetUrl(); }
public void ThrowTaskAgentExistException(AgentSettings agentSettings) { throw new TaskAgentExistsException(StringUtil.Loc("AgentWithSameNameAlreadyExistInPool", agentSettings.PoolId, agentSettings.AgentName)); }
//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()); } } }