// Return code definition: (this will be used by service host to determine whether it will re-launch agent.listener) // 0: Agent exit // 1: Terminate failure // 2: Retriable failure // 3: Exit for self update public async static Task <int> MainAsync(IHostContext context, string[] args) { Tracing trace = context.GetTrace("AgentProcess"); trace.Info($"Agent package {BuildConstants.AgentPackage.PackageName}."); trace.Info($"Running on {PlatformUtil.HostOS} ({PlatformUtil.HostArchitecture})."); trace.Info($"RuntimeInformation: {RuntimeInformation.OSDescription}."); context.WritePerfCounter("AgentProcessStarted"); var terminal = context.GetService <ITerminal>(); // TODO: check that the right supporting tools are available for this platform // (replaces the check for build platform vs runtime platform) try { trace.Info($"Version: {BuildConstants.AgentPackage.Version}"); trace.Info($"Commit: {BuildConstants.Source.CommitHash}"); trace.Info($"Culture: {CultureInfo.CurrentCulture.Name}"); trace.Info($"UI Culture: {CultureInfo.CurrentUICulture.Name}"); // Validate directory permissions. string agentDirectory = context.GetDirectory(WellKnownDirectory.Root); trace.Info($"Validating directory permissions for: '{agentDirectory}'"); try { IOUtil.ValidateExecutePermission(agentDirectory); } catch (Exception e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.TerminatedError); } if (PlatformUtil.RunningOnWindows) { // Validate PowerShell 3.0 or higher is installed. var powerShellExeUtil = context.GetService <IPowerShellExeUtil>(); try { powerShellExeUtil.GetPath(); } catch (Exception e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.TerminatedError); } // Validate .NET Framework 4.5 or higher is installed. if (!NetFrameworkUtil.Test(new Version(4, 5), trace)) { terminal.WriteError(StringUtil.Loc("MinimumNetFramework")); return(Constants.Agent.ReturnCode.TerminatedError); } } // Add environment variables from .env file string envFile = Path.Combine(context.GetDirectory(WellKnownDirectory.Root), ".env"); if (File.Exists(envFile)) { var envContents = File.ReadAllLines(envFile); foreach (var env in envContents) { if (!string.IsNullOrEmpty(env) && env.IndexOf('=') > 0) { string envKey = env.Substring(0, env.IndexOf('=')); string envValue = env.Substring(env.IndexOf('=') + 1); Environment.SetEnvironmentVariable(envKey, envValue); } } } // Parse the command line args. var command = new CommandSettings(context, args); trace.Info("Arguments parsed"); // Up front validation, warn for unrecognized commandline args. var unknownCommandlines = command.Validate(); if (unknownCommandlines.Count > 0) { terminal.WriteError(StringUtil.Loc("UnrecognizedCmdArgs", string.Join(", ", unknownCommandlines))); } // Defer to the Agent class to execute the command. IAgent agent = context.GetService <IAgent>(); try { return(await agent.ExecuteCommand(command)); } catch (OperationCanceledException) when(context.AgentShutdownToken.IsCancellationRequested) { trace.Info("Agent execution been cancelled."); return(Constants.Agent.ReturnCode.Success); } catch (NonRetryableException e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.TerminatedError); } } catch (Exception e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.RetryableError); } }
public 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(HostContext.GetDirectory(WellKnownDirectory.Externals), 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. if (!NetFrameworkUtil.Test(new Version(4, 6), Trace)) { 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(); var agentSvr = HostContext.GetService <IAgentServer>(); try { await agentSvr.ConnectAsync(new Uri(agentSettings.ServerUrl), credential); } 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 GetSourceAsync( AgentTaskPluginExecutionContext executionContext, Pipelines.RepositoryResource repository, CancellationToken cancellationToken) { // Validate args. ArgUtil.NotNull(executionContext, nameof(executionContext)); ArgUtil.NotNull(repository, nameof(repository)); // Validate .NET Framework 4.6 or higher is installed. if (PlatformUtil.RunningOnWindows && !NetFrameworkUtil.Test(new Version(4, 6), executionContext)) { throw new Exception(StringUtil.Loc("MinimumNetFramework46")); } // determine if we've been asked to suppress some checkout step output bool reducedOutput = AgentKnobs.QuietCheckout.GetValue(executionContext).AsBoolean(); if (reducedOutput) { executionContext.Output(StringUtil.Loc("QuietCheckoutModeRequested")); executionContext.SetTaskVariable(AgentKnobs.QuietCheckoutRuntimeVarName, Boolean.TrueString); } // Create the tf command manager. ITfsVCCliManager tf; if (PlatformUtil.RunningOnWindows) { tf = new TFCliManager(); } else { tf = new TeeCliManager(); } tf.CancellationToken = cancellationToken; tf.Repository = repository; tf.ExecutionContext = executionContext; if (repository.Endpoint != null) { // the endpoint should either be the SystemVssConnection (id = guild.empty, name = SystemVssConnection) // or a real service endpoint to external service which has a real id var endpoint = executionContext.Endpoints.Single( x => (repository.Endpoint.Id != Guid.Empty && x.Id == repository.Endpoint.Id) || (repository.Endpoint.Id == Guid.Empty && string.Equals(x.Name, repository.Endpoint.Name.ToString(), StringComparison.OrdinalIgnoreCase))); ArgUtil.NotNull(endpoint, nameof(endpoint)); tf.Endpoint = endpoint; } // Setup proxy. var agentProxy = executionContext.GetProxyConfiguration(); if (agentProxy != null && !string.IsNullOrEmpty(agentProxy.ProxyAddress) && !agentProxy.WebProxy.IsBypassed(repository.Url)) { executionContext.Debug($"Configure '{tf.FilePath}' to work through proxy server '{agentProxy.ProxyAddress}'."); tf.SetupProxy(agentProxy.ProxyAddress, agentProxy.ProxyUsername, agentProxy.ProxyPassword); } // Setup client certificate. var agentCertManager = executionContext.GetCertConfiguration(); if (agentCertManager != null && agentCertManager.SkipServerCertificateValidation) { executionContext.Debug("TF does not support ignoring SSL certificate validation error."); } // prepare client cert, if the repository's endpoint url match the TFS/VSTS url var systemConnection = executionContext.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase)); if (!string.IsNullOrEmpty(agentCertManager?.ClientCertificateFile) && Uri.Compare(repository.Url, systemConnection.Url, UriComponents.SchemeAndServer, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) == 0) { executionContext.Debug($"Configure '{tf.FilePath}' to work with client cert '{agentCertManager.ClientCertificateFile}'."); tf.SetupClientCertificate(agentCertManager.ClientCertificateFile, agentCertManager.ClientCertificatePrivateKeyFile, agentCertManager.ClientCertificateArchiveFile, agentCertManager.ClientCertificatePassword); } // Add TF to the PATH. string tfPath = tf.FilePath; ArgUtil.File(tfPath, nameof(tfPath)); executionContext.Output(StringUtil.Loc("Prepending0WithDirectoryContaining1", PathUtil.PathVariable, Path.GetFileName(tfPath))); executionContext.PrependPath(Path.GetDirectoryName(tfPath)); executionContext.Debug($"PATH: '{Environment.GetEnvironmentVariable("PATH")}'"); if (PlatformUtil.RunningOnWindows) { // Set TFVC_BUILDAGENT_POLICYPATH string policyDllPath = Path.Combine(executionContext.Variables.GetValueOrDefault("Agent.HomeDirectory")?.Value, "externals", "tf", "Microsoft.TeamFoundation.VersionControl.Controls.dll"); ArgUtil.File(policyDllPath, nameof(policyDllPath)); const string policyPathEnvKey = "TFVC_BUILDAGENT_POLICYPATH"; executionContext.Output(StringUtil.Loc("SetEnvVar", policyPathEnvKey)); executionContext.SetVariable(policyPathEnvKey, policyDllPath); } // Check if the administrator accepted the license terms of the TEE EULA when configuring the agent. if (tf.Features.HasFlag(TfsVCFeatures.Eula) && StringUtil.ConvertToBoolean(executionContext.Variables.GetValueOrDefault("Agent.AcceptTeeEula")?.Value)) { // 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.GetValueOrDefault("agent.builddirectory")?.Value; ArgUtil.NotNullOrEmpty(buildDirectory, nameof(buildDirectory)); string workspaceName = $"ws_{Path.GetFileName(buildDirectory)}_{executionContext.Variables.GetValueOrDefault("agent.id")?.Value}"; executionContext.SetVariable("build.repository.tfvc.workspace", workspaceName); // Get the definition mappings. var workspaceMappings = repository.Properties.Get <IList <Pipelines.WorkspaceMapping> >(Pipelines.RepositoryPropertyNames.Mappings); DefinitionWorkspaceMapping[] definitionMappings = workspaceMappings.Select(x => new DefinitionWorkspaceMapping() { ServerPath = x.ServerPath, LocalPath = x.LocalPath, MappingType = x.Exclude ? DefinitionMappingType.Cloak : DefinitionMappingType.Map }).ToArray(); // Determine the sources directory. string sourcesDirectory = repository.Properties.Get <string>(Pipelines.RepositoryPropertyNames.Path); 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 = StringUtil.ConvertToBoolean(executionContext.GetInput(Pipelines.PipelineConstants.CheckoutTaskInputs.Clean)); 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.TryWorkspaceDeleteAsync(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, quiet : reducedOutput); } 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), quiet : reducedOutput); } } } // Steps for shelveset/gated. string shelvesetName = repository.Properties.Get <string>(Pipelines.RepositoryPropertyNames.Shelveset); if (!string.IsNullOrEmpty(shelvesetName)) { // Steps for gated. ITfsVCShelveset tfShelveset = null; string gatedShelvesetName = executionContext.Variables.GetValueOrDefault("build.gated.shelvesetname")?.Value; 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. bool unshelveErrorsAllowed = AgentKnobs.AllowTfvcUnshelveErrors.GetValue(executionContext).AsBoolean(); await tf.UnshelveAsync(shelveset : shelvesetName, unshelveErrorsAllowed); // Ensure we undo pending changes for shelveset build at the end. executionContext.SetTaskVariable("UndoShelvesetPendingChanges", bool.TrueString); if (!string.IsNullOrEmpty(gatedShelvesetName)) { // Create the comment file for reshelve. StringBuilder comment = new StringBuilder(tfShelveset.Comment ?? string.Empty); string runCi = executionContext.Variables.GetValueOrDefault("build.gated.runci")?.Value; bool gatedRunCi = StringUtil.ConvertToBoolean(runCi, true); if (!gatedRunCi) { if (comment.Length > 0) { comment.AppendLine(); } comment.Append("***NO_CI***"); } 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 (agentProxy != null && !string.IsNullOrEmpty(agentProxy.ProxyAddress) && !agentProxy.WebProxy.IsBypassed(repository.Url)) { executionContext.Debug($"Remove proxy setting for '{tf.FilePath}' to work through proxy server '{agentProxy.ProxyAddress}'."); tf.CleanupProxySetting(); } // Set intra-task variable for post job cleanup executionContext.SetTaskVariable("repository", repository.Alias); }
// Return code definition: (this will be used by service host to determine whether it will re-launch agent.listener) // 0: Agent exit // 1: Terminate failure // 2: Retriable failure // 3: Exit for self update public async static Task <int> MainAsync(IHostContext context, string[] args) { Tracing trace = context.GetTrace("AgentProcess"); trace.Info($"Agent is built for {Constants.Agent.Platform} - {BuildConstants.AgentPackage.PackageName}."); trace.Info($"RuntimeInformation: {RuntimeInformation.OSDescription}."); context.WritePerfCounter("AgentProcessStarted"); var terminal = context.GetService <ITerminal>(); // Validate the binaries intended for one OS are not running on a different OS. switch (Constants.Agent.Platform) { case Constants.OSPlatform.Linux: if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { terminal.WriteLine(StringUtil.Loc("NotLinux")); return(Constants.Agent.ReturnCode.TerminatedError); } break; case Constants.OSPlatform.OSX: if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { terminal.WriteLine(StringUtil.Loc("NotOSX")); return(Constants.Agent.ReturnCode.TerminatedError); } break; case Constants.OSPlatform.Windows: if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { terminal.WriteLine(StringUtil.Loc("NotWindows")); return(Constants.Agent.ReturnCode.TerminatedError); } break; default: terminal.WriteLine(StringUtil.Loc("PlatformNotSupport", RuntimeInformation.OSDescription, Constants.Agent.Platform.ToString())); return(Constants.Agent.ReturnCode.TerminatedError); } try { trace.Info($"Version: {Constants.Agent.Version}"); trace.Info($"Commit: {BuildConstants.Source.CommitHash}"); trace.Info($"Culture: {CultureInfo.CurrentCulture.Name}"); trace.Info($"UI Culture: {CultureInfo.CurrentUICulture.Name}"); // Validate directory permissions. string agentDirectory = context.GetDirectory(WellKnownDirectory.Root); trace.Info($"Validating directory permissions for: '{agentDirectory}'"); try { IOUtil.ValidateExecutePermission(agentDirectory); } catch (Exception e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.TerminatedError); } #if OS_WINDOWS // Validate PowerShell 3.0 or higher is installed. var powerShellExeUtil = context.GetService <IPowerShellExeUtil>(); try { powerShellExeUtil.GetPath(); } catch (Exception e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.TerminatedError); } // Validate .NET Framework 4.5 or higher is installed. if (!NetFrameworkUtil.Test(new Version(4, 5), trace)) { terminal.WriteError(StringUtil.Loc("MinimumNetFramework")); return(Constants.Agent.ReturnCode.TerminatedError); } #endif // Add environment variables from .env file string envFile = Path.Combine(context.GetDirectory(WellKnownDirectory.Root), ".env"); if (File.Exists(envFile)) { var envContents = File.ReadAllLines(envFile); foreach (var env in envContents) { if (!string.IsNullOrEmpty(env) && env.IndexOf('=') > 0) { string envKey = env.Substring(0, env.IndexOf('=')); string envValue = env.Substring(env.IndexOf('=') + 1); Environment.SetEnvironmentVariable(envKey, envValue); } } } // Parse the command line args. var command = new CommandSettings(context, args); trace.Info("Arguments parsed"); // Up front validation, warn for unrecognized commandline args. var unknownCommandlines = command.Validate(); if (unknownCommandlines.Count > 0) { terminal.WriteError(StringUtil.Loc("UnrecognizedCmdArgs", string.Join(", ", unknownCommandlines))); } // Defer to the Agent class to execute the command. IAgent agent = context.GetService <IAgent>(); try { return(await agent.ExecuteCommand(command)); } catch (OperationCanceledException) when(context.AgentShutdownToken.IsCancellationRequested) { trace.Info("Agent execution been cancelled."); return(Constants.Agent.ReturnCode.Success); } catch (NonRetryableException e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.TerminatedError); } } catch (Exception e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.RetryableError); } }
public 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. if (!NetFrameworkUtil.Test(new Version(4, 6), Trace)) { 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.WebProxy.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); } // Setup client certificate. var agentCertManager = HostContext.GetService <IAgentCertificateManager>(); if (agentCertManager.SkipServerCertificateValidation) { #if OS_WINDOWS executionContext.Debug("TF.exe does not support ignore SSL certificate validation error."); #else executionContext.Debug("TF does not support ignore SSL certificate validation error."); #endif } var configUrl = new Uri(HostContext.GetService <IConfigurationStore>().GetSettings().ServerUrl); if (!string.IsNullOrEmpty(agentCertManager.ClientCertificateFile) && Uri.Compare(endpoint.Url, configUrl, UriComponents.SchemeAndServer, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) == 0) { executionContext.Debug($"Configure '{tf.FilePath}' to work with client cert '{agentCertManager.ClientCertificateFile}'."); tf.SetupClientCertificate(agentCertManager.ClientCertificateFile, agentCertManager.ClientCertificatePrivateKeyFile, agentCertManager.ClientCertificateArchiveFile, agentCertManager.ClientCertificatePassword); } // Add TF to the PATH. string tfPath = tf.FilePath; ArgUtil.File(tfPath, nameof(tfPath)); executionContext.Output(StringUtil.Loc("Prepending0WithDirectoryContaining1", Constants.PathVariable, Path.GetFileName(tfPath))); PathUtil.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[EndpointData.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(EndpointData.Clean) && StringUtil.ConvertToBoolean(endpoint.Data[EndpointData.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.WebProxy.IsBypassed(endpoint.Url)) { executionContext.Debug($"Remove proxy setting for '{tf.FilePath}' to work through proxy server '{executionContext.Variables.Agent_ProxyUrl}'."); tf.CleanupProxySetting(); } }
// Return code definition: (this will be used by service host to determine whether it will re-launch agent.listener) // 0: Agent exit // 1: Terminate failure // 2: Retriable failure // 3: Exit for self update private async static Task <int> MainAsync(IHostContext context, string[] args) { Tracing trace = context.GetTrace("AgentProcess"); trace.Info($"Agent package {BuildConstants.AgentPackage.PackageName}."); trace.Info($"Running on {PlatformUtil.HostOS} ({PlatformUtil.HostArchitecture})."); trace.Info($"RuntimeInformation: {RuntimeInformation.OSDescription}."); context.WritePerfCounter("AgentProcessStarted"); var terminal = context.GetService <ITerminal>(); // TODO: check that the right supporting tools are available for this platform // (replaces the check for build platform vs runtime platform) try { trace.Info($"Version: {BuildConstants.AgentPackage.Version}"); trace.Info($"Commit: {BuildConstants.Source.CommitHash}"); trace.Info($"Culture: {CultureInfo.CurrentCulture.Name}"); trace.Info($"UI Culture: {CultureInfo.CurrentUICulture.Name}"); // Validate directory permissions. string agentDirectory = context.GetDirectory(WellKnownDirectory.Root); trace.Info($"Validating directory permissions for: '{agentDirectory}'"); try { IOUtil.ValidateExecutePermission(agentDirectory); } catch (Exception e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.TerminatedError); } if (PlatformUtil.UseLegacyHttpHandler) { trace.Warning($"You are using the legacy HTTP handler because you set ${AgentKnobs.LegacyHttpVariableName}."); trace.Warning($"This feature will go away with .NET 5.0, and we recommend you don't use it."); trace.Warning($"If you continue using it, you must ensure libcurl is installed on your system."); } if (PlatformUtil.RunningOnWindows) { // Validate PowerShell 3.0 or higher is installed. var powerShellExeUtil = context.GetService <IPowerShellExeUtil>(); try { powerShellExeUtil.GetPath(); } catch (Exception e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.TerminatedError); } // Validate .NET Framework 4.5 or higher is installed. if (!NetFrameworkUtil.Test(new Version(4, 5), trace)) { terminal.WriteError(StringUtil.Loc("MinimumNetFramework")); // warn only, like configurationmanager.cs does. this enables windows edition with just .netcore to work } // Upgrade process priority to avoid Listener starvation using (Process p = Process.GetCurrentProcess()) { try { p.PriorityClass = ProcessPriorityClass.AboveNormal; } catch (Exception e) { trace.Warning("Unable to change Windows process priority"); trace.Warning(e.Message); } } } // Add environment variables from .env file string envFile = Path.Combine(context.GetDirectory(WellKnownDirectory.Root), ".env"); if (File.Exists(envFile)) { var envContents = File.ReadAllLines(envFile); foreach (var env in envContents) { if (!string.IsNullOrEmpty(env) && env.IndexOf('=') > 0) { string envKey = env.Substring(0, env.IndexOf('=')); string envValue = env.Substring(env.IndexOf('=') + 1); Environment.SetEnvironmentVariable(envKey, envValue); } } } // Parse the command line args. var command = new CommandSettings(context, args, new SystemEnvironment()); trace.Info("Arguments parsed"); // Print any Parse Errros if (command.ParseErrors?.Any() == true) { List <string> errorStr = new List <string>(); foreach (var error in command.ParseErrors) { if (error is TokenError tokenError) { errorStr.Add(tokenError.Token); } else { // Unknown type of error dump to log terminal.WriteError(StringUtil.Loc("ErrorOccurred", error.Tag)); } } terminal.WriteError( StringUtil.Loc("UnrecognizedCmdArgs", string.Join(", ", errorStr))); } // Defer to the Agent class to execute the command. IAgent agent = context.GetService <IAgent>(); try { return(await agent.ExecuteCommand(command)); } catch (OperationCanceledException) when(context.AgentShutdownToken.IsCancellationRequested) { trace.Info("Agent execution been cancelled."); return(Constants.Agent.ReturnCode.Success); } catch (NonRetryableException e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.TerminatedError); } } catch (Exception e) { terminal.WriteError(StringUtil.Loc("ErrorOccurred", e.Message)); trace.Error(e); return(Constants.Agent.ReturnCode.RetryableError); } }