public async Task GetSourceAsync( IExecutionContext executionContext, ServiceEndpoint endpoint, CancellationToken cancellationToken) { // Validate args. ArgUtil.NotNull(executionContext, nameof(executionContext)); ArgUtil.NotNull(endpoint, nameof(endpoint)); // Create the tf command manager. var tf = HostContext.CreateService <ITeeTFCommandManager>(); tf.CancellationToken = cancellationToken; tf.Endpoint = endpoint; tf.ExecutionContext = executionContext; // Get the TEE workspaces. TeeWorkspace[] teeWorkspaces = await tf.WorkspacesAsync(); // Determine the workspace name. AgentSettings settings = HostContext.GetService <IConfigurationStore>().GetSettings(); 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. TfsVCTeeWorkspaceMapping[] definitionMappings = JsonConvert.DeserializeObject <TfsVCTeeWorkspaceMappings>(endpoint.Data["tfvcWorkspaceMapping"])?.Mappings; // Determine the sources directory. string sourcesDirectory = executionContext.Variables.Build_SourcesDirectory; ArgUtil.NotNullOrEmpty(sourcesDirectory, nameof(sourcesDirectory)); // Attempt to re-use an existing workspace if clean=false. TeeWorkspace existingTeeWorkspace = null; bool clean = endpoint.Data.ContainsKey(WellKnownEndpointData.Clean) && StringUtil.ConvertToBoolean(endpoint.Data[WellKnownEndpointData.Clean], defaultValue: false); if (!clean) { existingTeeWorkspace = MatchExactWorkspace( teeWorkspaces: teeWorkspaces, name: workspaceName, definitionMappings: definitionMappings, sourcesDirectory: sourcesDirectory); // Undo any pending changes. // TODO: Manually delete pending adds since they do not get deleted on undo? if (existingTeeWorkspace != null) { TeeStatus teeStatus = await tf.StatusAsync(workspaceName); if (teeStatus?.PendingChanges?.Any() ?? false) { await tf.UndoAsync(sourcesDirectory); } } } // Create a new workspace. if (existingTeeWorkspace == null) { // Remove any conflicting TEE workspaces. await RemoveConflictingWorkspacesAsync( tf : tf, teeWorkspaces : teeWorkspaces, name : workspaceName, directory : sourcesDirectory); // Recreate the sources directory. executionContext.Debug($"Deleting: '{sourcesDirectory}'."); IOUtil.DeleteDirectory(sourcesDirectory, cancellationToken); Directory.CreateDirectory(sourcesDirectory); // Create the TEE workspace. await tf.WorkspaceNewAsync(workspaceName); // Sort the definition mappings. definitionMappings = (definitionMappings ?? new TfsVCTeeWorkspaceMapping[0]) .OrderBy(x => NormalizeServerPath(x.ServerPath)?.Length ?? 0) // By server path length. .ToArray() ?? new TfsVCTeeWorkspaceMapping[0]; // Add the definition mappings to the TEE workspace. foreach (TfsVCTeeWorkspaceMapping definitionMapping in definitionMappings) { switch (definitionMapping.MappingType) { case TfsVCTeeMappingType.Cloak: // Add the cloak. await tf.WorkfoldCloakAsync( workspace : workspaceName, serverPath : definitionMapping.ServerPath); break; case TfsVCTeeMappingType.Map: // Add the mapping. await tf.WorkfoldMapAsync( workspace : workspaceName, serverPath : definitionMapping.ServerPath, localPath : ResolveMappingLocalPath(definitionMapping, sourcesDirectory)); break; default: throw new NotSupportedException(); } } } // Get. await tf.GetAsync( version : executionContext.Variables.Build_SourceVersion, directory : sourcesDirectory); string shelvesetName = executionContext.Variables.Build_SourceTfvcShelveset; if (!string.IsNullOrEmpty(shelvesetName)) { // Get the shelveset details. TeeShelveset teeShelveset = null; string gatedShelvesetName = executionContext.Variables.Build_GatedShelvesetName; if (!string.IsNullOrEmpty(gatedShelvesetName)) { teeShelveset = await tf.ShelvesetsAsync(workspace : workspaceName, shelveset : shelvesetName); // The command throws if the shelveset is not found. // This assertion should never fail. ArgUtil.NotNull(teeShelveset, nameof(teeShelveset)); } // Unshelve. // TODO: Confirm Get then Unshelve is OK. await tf.UnshelveAsync(workspace : workspaceName, shelveset : shelvesetName); if (!string.IsNullOrEmpty(gatedShelvesetName)) { // Create the comment file for reshelve. StringBuilder comment = new StringBuilder(teeShelveset.Comment ?? string.Empty); if (!(executionContext.Variables.Build_GatedRunCI ?? true)) { if (comment.Length > 0) { comment.AppendLine(); } comment.Append(Constants.Build.NoCICheckInComment); } string commentFile = null; try { commentFile = Path.GetTempFileName(); // TODO: FIGURE OUT WHAT ENCODING TF EXPECTS File.WriteAllText(path: commentFile, contents: comment.ToString()); // Reshelve. // TODO: Work with TEE folks regarding support for associate work items, policy override comment, etc. await tf.ShelveAsync( directory : sourcesDirectory, shelveset : gatedShelvesetName, commentFile : commentFile); } finally { // Cleanup the comment file. if (File.Exists(commentFile)) { File.Delete(commentFile); } } } } }
public async Task GetSourceAsync( IExecutionContext executionContext, ServiceEndpoint endpoint, CancellationToken cancellationToken) { // Validate args. ArgUtil.NotNull(executionContext, nameof(executionContext)); ArgUtil.NotNull(endpoint, nameof(endpoint)); // Create the tf command manager. var tf = HostContext.CreateService <ITeeTFCommandManager>(); tf.CancellationToken = cancellationToken; tf.Endpoint = endpoint; tf.ExecutionContext = executionContext; // Check if the administrator accepted the license terms of the TEE EULA when // configuring the agent. AgentSettings settings = HostContext.GetService <IConfigurationStore>().GetSettings(); if (settings.AcceptTeeEula) { // Check if the "tf eula -accept" command needs to be run for the current security user. bool skipEula = false; try { skipEula = tf.TestEulaAccepted(); } catch (Exception ex) { Trace.Error("Unexpected exception while testing whether the TEE EULA has been accepted for the current security user."); Trace.Error(ex); } if (!skipEula) { // Run the command "tf eula -accept". try { await tf.EulaAsync(); } catch (Exception ex) { Trace.Error(ex); executionContext.Warning(ex.Message); } } } // Get the TEE workspaces. TeeWorkspace[] teeWorkspaces = 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. TfsVCTeeWorkspaceMapping[] definitionMappings = JsonConvert.DeserializeObject <TfsVCTeeWorkspaceMappings>(endpoint.Data["tfvcWorkspaceMapping"])?.Mappings; // Determine the sources directory. string sourcesDirectory = executionContext.Variables.Build_SourcesDirectory; ArgUtil.NotNullOrEmpty(sourcesDirectory, nameof(sourcesDirectory)); // Attempt to re-use an existing workspace if clean=false. TeeWorkspace existingTeeWorkspace = null; bool clean = endpoint.Data.ContainsKey(WellKnownEndpointData.Clean) && StringUtil.ConvertToBoolean(endpoint.Data[WellKnownEndpointData.Clean], defaultValue: false); if (!clean) { existingTeeWorkspace = MatchExactWorkspace( teeWorkspaces: teeWorkspaces, name: workspaceName, definitionMappings: definitionMappings, sourcesDirectory: sourcesDirectory); // Undo any pending changes. if (existingTeeWorkspace != null) { TeeStatus teeStatus = await tf.StatusAsync(workspaceName); if (teeStatus?.PendingChanges?.Any() ?? false) { await tf.UndoAsync(sourcesDirectory); // Cleanup remaining files/directories from pend adds. teeStatus.PendingChanges .Where(x => string.Equals(x.ChangeType, "add", StringComparison.OrdinalIgnoreCase)) .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); }); } } } // Create a new workspace. if (existingTeeWorkspace == null) { // Remove any conflicting TEE workspaces. await RemoveConflictingWorkspacesAsync( tf : tf, teeWorkspaces : teeWorkspaces, name : workspaceName, directory : sourcesDirectory); // Recreate the sources directory. executionContext.Debug($"Deleting: '{sourcesDirectory}'."); IOUtil.DeleteDirectory(sourcesDirectory, cancellationToken); Directory.CreateDirectory(sourcesDirectory); // Create the TEE workspace. await tf.WorkspaceNewAsync(workspaceName); // Sort the definition mappings. definitionMappings = (definitionMappings ?? new TfsVCTeeWorkspaceMapping[0]) .OrderBy(x => NormalizeServerPath(x.ServerPath)?.Length ?? 0) // By server path length. .ToArray() ?? new TfsVCTeeWorkspaceMapping[0]; // Add the definition mappings to the TEE workspace. foreach (TfsVCTeeWorkspaceMapping definitionMapping in definitionMappings) { switch (definitionMapping.MappingType) { case TfsVCTeeMappingType.Cloak: // Add the cloak. await tf.WorkfoldCloakAsync( workspace : workspaceName, serverPath : definitionMapping.ServerPath); break; case TfsVCTeeMappingType.Map: // Add the mapping. await tf.WorkfoldMapAsync( workspace : workspaceName, serverPath : definitionMapping.ServerPath, localPath : ResolveMappingLocalPath(definitionMapping, sourcesDirectory)); break; default: throw new NotSupportedException(); } } } // Get. await tf.GetAsync( version : executionContext.Variables.Build_SourceVersion, directory : sourcesDirectory); string shelvesetName = executionContext.Variables.Build_SourceTfvcShelveset; if (!string.IsNullOrEmpty(shelvesetName)) { // Get the shelveset details. TeeShelveset teeShelveset = null; string gatedShelvesetName = executionContext.Variables.Build_GatedShelvesetName; if (!string.IsNullOrEmpty(gatedShelvesetName)) { teeShelveset = await tf.ShelvesetsAsync(workspace : workspaceName, shelveset : shelvesetName); // The command throws if the shelveset is not found. // This assertion should never fail. ArgUtil.NotNull(teeShelveset, nameof(teeShelveset)); } // Unshelve. await tf.UnshelveAsync(workspace : workspaceName, shelveset : shelvesetName); if (!string.IsNullOrEmpty(gatedShelvesetName)) { // Create the comment file for reshelve. StringBuilder comment = new StringBuilder(teeShelveset.Comment ?? string.Empty); if (!(executionContext.Variables.Build_GatedRunCI ?? true)) { if (comment.Length > 0) { comment.AppendLine(); } comment.Append(Constants.Build.NoCICheckInComment); } string commentFile = null; try { commentFile = Path.GetTempFileName(); // TODO: FIGURE OUT WHAT ENCODING TF EXPECTS File.WriteAllText(path: commentFile, contents: comment.ToString()); // Reshelve. await tf.ShelveAsync( shelveset : gatedShelvesetName, directory : sourcesDirectory, commentFile : commentFile); } finally { // Cleanup the comment file. if (File.Exists(commentFile)) { File.Delete(commentFile); } } } } }