public void Debug(string message) { if (PluginUtil.ConvertToBoolean(this.Variables.GetValueOrDefault("system.debug")?.Value)) { Output($"##[debug]{message}"); } }
private AgentCertificateSettings GetCertConfiguration() { bool skipCertValidation = PluginUtil.ConvertToBoolean(this.Variables.GetValueOrDefault("Agent.SkipCertValidation")?.Value); string caFile = this.Variables.GetValueOrDefault("Agent.CAInfo")?.Value; string clientCertFile = this.Variables.GetValueOrDefault("Agent.ClientCert")?.Value; if (!string.IsNullOrEmpty(caFile) || !string.IsNullOrEmpty(clientCertFile) || skipCertValidation) { var certConfig = new AgentCertificateSettings(); certConfig.SkipServerCertificateValidation = skipCertValidation; certConfig.CACertificateFile = caFile; if (!string.IsNullOrEmpty(clientCertFile)) { certConfig.ClientCertificateFile = clientCertFile; string clientCertKey = this.Variables.GetValueOrDefault("Agent.ClientCertKey")?.Value; string clientCertArchive = this.Variables.GetValueOrDefault("Agent.ClientCertArchive")?.Value; string clientCertPassword = this.Variables.GetValueOrDefault("Agent.ClientCertPassword")?.Value; certConfig.ClientCertificatePrivateKeyFile = clientCertKey; certConfig.ClientCertificateArchiveFile = clientCertArchive; certConfig.ClientCertificatePassword = clientCertPassword; } return(certConfig); } else { return(null); } }
protected void MergeInputs(AgentTaskPluginExecutionContext executionContext, Pipelines.RepositoryResource repository) { string clean = executionContext.GetInput("clean"); if (!string.IsNullOrEmpty(clean)) { repository.Properties.Set <bool>("clean", PluginUtil.ConvertToBoolean(clean)); } // there is no addition inputs for TFVC and SVN if (repository.Type == RepositoryTypes.Bitbucket || repository.Type == RepositoryTypes.GitHub || repository.Type == RepositoryTypes.GitHubEnterprise || repository.Type == RepositoryTypes.Git || repository.Type == RepositoryTypes.TfsGit) { string checkoutSubmodules = executionContext.GetInput("checkoutSubmodules"); if (!string.IsNullOrEmpty(checkoutSubmodules)) { repository.Properties.Set <bool>("checkoutSubmodules", PluginUtil.ConvertToBoolean(checkoutSubmodules)); } string checkoutNestedSubmodules = executionContext.GetInput("checkoutNestedSubmodules"); if (!string.IsNullOrEmpty(checkoutNestedSubmodules)) { repository.Properties.Set <bool>("checkoutNestedSubmodules", PluginUtil.ConvertToBoolean(checkoutNestedSubmodules)); } string preserveCredential = executionContext.GetInput("preserveCredential"); if (!string.IsNullOrEmpty(preserveCredential)) { repository.Properties.Set <bool>("preserveCredential", PluginUtil.ConvertToBoolean(preserveCredential)); } string gitLfsSupport = executionContext.GetInput("gitLfsSupport"); if (!string.IsNullOrEmpty(gitLfsSupport)) { repository.Properties.Set <bool>("gitLfsSupport", PluginUtil.ConvertToBoolean(gitLfsSupport)); } string acceptUntrustedCerts = executionContext.GetInput("acceptUntrustedCerts"); if (!string.IsNullOrEmpty(acceptUntrustedCerts)) { repository.Properties.Set <bool>("acceptUntrustedCerts", PluginUtil.ConvertToBoolean(acceptUntrustedCerts)); } string fetchDepth = executionContext.GetInput("fetchDepth"); if (!string.IsNullOrEmpty(fetchDepth)) { repository.Properties.Set <string>("fetchDepth", fetchDepth); } } }
/// <summary> /// Initializes svn command path and execution environment /// </summary> /// <param name="context">The build commands' execution context</param> /// <param name="endpoint">The Subversion server endpoint providing URL, username/password, and untrasted certs acceptace information</param> /// <param name="cancellationToken">The cancellation token used to stop svn command execution</param> public void Init( AgentTaskPluginExecutionContext context, Pipelines.RepositoryResource repository, CancellationToken cancellationToken) { // Validation. PluginUtil.NotNull(context, nameof(context)); PluginUtil.NotNull(repository, nameof(repository)); PluginUtil.NotNull(cancellationToken, nameof(cancellationToken)); PluginUtil.NotNull(repository.Url, nameof(repository.Url)); PluginUtil.Equal(true, repository.Url.IsAbsoluteUri, nameof(repository.Url.IsAbsoluteUri)); PluginUtil.NotNull(repository.Endpoint, nameof(repository.Endpoint)); ServiceEndpoint endpoint = context.Endpoints.Single(x => x.Id == repository.Endpoint.Id); PluginUtil.NotNull(endpoint.Data, nameof(endpoint.Data)); PluginUtil.NotNull(endpoint.Authorization, nameof(endpoint.Authorization)); PluginUtil.NotNull(endpoint.Authorization.Parameters, nameof(endpoint.Authorization.Parameters)); PluginUtil.Equal(EndpointAuthorizationSchemes.UsernamePassword, endpoint.Authorization.Scheme, nameof(endpoint.Authorization.Scheme)); _context = context; _repository = repository; _cancellationToken = cancellationToken; // Find svn in %Path% string svnPath = PluginUtil.Which("svn"); if (string.IsNullOrEmpty(svnPath)) { throw new Exception(PluginUtil.Loc("SvnNotInstalled")); } else { _context.Debug($"Found svn installation path: {svnPath}."); _svn = svnPath; } // External providers may need basic auth or tokens endpoint.Authorization.Parameters.TryGetValue(EndpointAuthorizationParameters.Username, out _username); endpoint.Authorization.Parameters.TryGetValue(EndpointAuthorizationParameters.Password, out _password); _acceptUntrusted = PluginUtil.ConvertToBoolean(repository.Properties.Get <string>(EndpointData.SvnAcceptUntrustedCertificates)); }
public async Task GetSourceAsync( AgentTaskPluginExecutionContext executionContext, Pipelines.RepositoryResource repository, CancellationToken cancellationToken) { // Validate args. PluginUtil.NotNull(executionContext, nameof(executionContext)); PluginUtil.NotNull(repository, nameof(repository)); #if OS_WINDOWS // Validate .NET Framework 4.6 or higher is installed. if (!PluginUtil.TestNetFrameworkVersion(executionContext, new Version(4, 6))) { throw new Exception(PluginUtil.Loc("MinimumNetFramework46")); } #endif // Create the tf command manager. #if OS_WINDOWS var tf = new TFCliManager(); #else var tf = new TeeCliManager(); #endif 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 => (x.Id == Guid.Empty && x.Name == repository.Endpoint.Name) || (x.Id != Guid.Empty && x.Id == repository.Endpoint.Id)); PluginUtil.NotNull(endpoint, nameof(endpoint)); tf.Endpoint = endpoint; } // Setup proxy. var agentProxy = executionContext.GetProxyConfiguration(); if (agentProxy != null && !string.IsNullOrEmpty(agentProxy.ProxyAddress) && !agentProxy.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) { #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 } // 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; PluginUtil.FileExists(tfPath, nameof(tfPath)); #if OS_WINDOWS executionContext.Output(PluginUtil.Loc("Prepending0WithDirectoryContaining1", "Path", Path.GetFileName(tfPath))); #else executionContext.Output(PluginUtil.Loc("Prepending0WithDirectoryContaining1", "PATH", Path.GetFileName(tfPath))); #endif executionContext.PrependPath(Path.GetDirectoryName(tfPath)); executionContext.Debug($"PATH: '{Environment.GetEnvironmentVariable("PATH")}'"); #if OS_WINDOWS // Set TFVC_BUILDAGENT_POLICYPATH string policyDllPath = Path.Combine(executionContext.Variables.GetValueOrDefault("Agent.ServerOMDirectory")?.Value, "Microsoft.TeamFoundation.VersionControl.Controls.dll"); PluginUtil.FileExists(policyDllPath, nameof(policyDllPath)); const string policyPathEnvKey = "TFVC_BUILDAGENT_POLICYPATH"; executionContext.Output(PluginUtil.Loc("SetEnvVar", policyPathEnvKey)); Environment.SetEnvironmentVariable(policyPathEnvKey, policyDllPath); #endif // Check if the administrator accepted the license terms of the TEE EULA when configuring the agent. if (tf.Features.HasFlag(TfsVCFeatures.Eula) && PluginUtil.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(PluginUtil.Loc("QueryingWorkspaceInfo")); ITfsVCWorkspace[] tfWorkspaces = await tf.WorkspacesAsync(); // Determine the workspace name. string buildDirectory = executionContext.Variables.GetValueOrDefault("agent.builddirectory")?.Value; PluginUtil.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. DefinitionWorkspaceMapping[] definitionMappings = JsonConvert.DeserializeObject <DefinitionWorkspaceMappings>(repository.Properties.Get <string>(EndpointData.TfvcWorkspaceMapping))?.Mappings; // Determine the sources directory. string sourcesDirectory = repository.Properties.Get <string>("sourcedirectory"); PluginUtil.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 = PluginUtil.ConvertToBoolean(repository.Properties.Get <string>(EndpointData.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(PluginUtil.Loc("Deleting", x.LocalItem)); PluginUtil.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(PluginUtil.Loc("Deleting", x.LocalItem)); PluginUtil.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}'."); PluginUtil.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 = repository.Properties.Get <string>("SourceTfvcShelveset"); if (!string.IsNullOrEmpty(shelvesetName)) { // Steps for gated. ITfsVCShelveset tfShelveset = null; string gatedShelvesetName = repository.Properties.Get <string>("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 { PluginUtil.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. PluginUtil.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 = repository.Properties.Get <string>("GatedRunCI"); bool gatedRunCi = PluginUtil.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.IsBypassed(repository.Url)) { executionContext.Debug($"Remove proxy setting for '{tf.FilePath}' to work through proxy server '{agentProxy.ProxyAddress}'."); tf.CleanupProxySetting(); } }
public async Task GetSourceAsync( AgentTaskPluginExecutionContext executionContext, Pipelines.RepositoryResource repository, CancellationToken cancellationToken) { // Validate args. PluginUtil.NotNull(executionContext, nameof(executionContext)); PluginUtil.NotNull(repository, nameof(repository)); SvnCliManager svn = new SvnCliManager(); svn.Init(executionContext, repository, cancellationToken); // Determine the sources directory. string sourcesDirectory = repository.Properties.Get <string>("sourcedirecotry"); executionContext.Debug($"sourcesDirectory={sourcesDirectory}"); PluginUtil.NotNullOrEmpty(sourcesDirectory, nameof(sourcesDirectory)); string sourceBranch = repository.Properties.Get <string>("sourcebranch"); executionContext.Debug($"sourceBranch={sourceBranch}"); string revision = repository.Version; if (string.IsNullOrWhiteSpace(revision)) { revision = "HEAD"; } executionContext.Debug($"revision={revision}"); bool clean = PluginUtil.ConvertToBoolean(repository.Properties.Get <string>(EndpointData.Clean)); executionContext.Debug($"clean={clean}"); // Get the definition mappings. List <SvnMappingDetails> allMappings = JsonConvert.DeserializeObject <SvnWorkspace>(repository.Properties.Get <string>(EndpointData.SvnWorkspaceMapping)).Mappings; if (PluginUtil.ConvertToBoolean(executionContext.Variables.GetValueOrDefault("system.debug")?.Value)) { allMappings.ForEach(m => executionContext.Debug($"ServerPath: {m.ServerPath}, LocalPath: {m.LocalPath}, Depth: {m.Depth}, Revision: {m.Revision}, IgnoreExternals: {m.IgnoreExternals}")); } Dictionary <string, SvnMappingDetails> normalizedMappings = svn.NormalizeMappings(allMappings); if (PluginUtil.ConvertToBoolean(executionContext.Variables.GetValueOrDefault("system.debug")?.Value)) { executionContext.Debug($"Normalized mappings count: {normalizedMappings.Count}"); normalizedMappings.ToList().ForEach(p => executionContext.Debug($" [{p.Key}] ServerPath: {p.Value.ServerPath}, LocalPath: {p.Value.LocalPath}, Depth: {p.Value.Depth}, Revision: {p.Value.Revision}, IgnoreExternals: {p.Value.IgnoreExternals}")); } string normalizedBranch = svn.NormalizeRelativePath(sourceBranch, '/', '\\'); executionContext.Output(PluginUtil.Loc("SvnSyncingRepo", repository.Properties.Get <string>("name"))); string effectiveRevision = await svn.UpdateWorkspace( sourcesDirectory, normalizedMappings, clean, normalizedBranch, revision); executionContext.Output(PluginUtil.Loc("SvnBranchCheckedOut", normalizedBranch, repository.Properties.Get <string>("name"), effectiveRevision)); }