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<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 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 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 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 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 = [email protected]"-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; }
//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()); } } }
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) { }
//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; }
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); } } }
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 abstract bool ConfigureService(AgentSettings settings, CommandSettings command);
public abstract void GenerateScripts(AgentSettings settings);