Ejemplo n.º 1
0
        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);
                        }
                    }
                }
            }
        }