Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update TF.exe and binding redirects for vstshost #4990

Merged
merged 3 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/Agent.Plugins/TFCliManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System.Xml;
using System.Security.Cryptography.X509Certificates;
using Microsoft.VisualStudio.Services.Agent.Util;
using Agent.Sdk.Knob;

namespace Agent.Plugins.Repository
{
Expand All @@ -37,11 +38,15 @@ public override TfsVCFeatures Features

public static readonly int RetriesOnFailure = 3;

public string FilePath => Path.Combine(ExecutionContext.Variables.GetValueOrDefault("Agent.HomeDirectory")?.Value, "externals", "tf", "tf.exe");
private string TfPath => AgentKnobs.InstallLegacyTfExe.GetValue(ExecutionContext).AsBoolean()
? Path.Combine(ExecutionContext.Variables.GetValueOrDefault("Agent.HomeDirectory")?.Value, "externals", "tf-legacy")
: Path.Combine(ExecutionContext.Variables.GetValueOrDefault("Agent.HomeDirectory")?.Value, "externals", "tf");

private string AppConfigFile => Path.Combine(ExecutionContext.Variables.GetValueOrDefault("Agent.HomeDirectory")?.Value, "externals", "tf", "tf.exe.config");
public string FilePath => Path.Combine(TfPath, "tf.exe");

private string AppConfigRestoreFile => Path.Combine(ExecutionContext.Variables.GetValueOrDefault("Agent.HomeDirectory")?.Value, "externals", "tf", "tf.exe.config.restore");
private string AppConfigFile => Path.Combine(TfPath, "tf.exe.config");

private string AppConfigRestoreFile => Path.Combine(TfPath, "tf.exe.config.restore");

// TODO: Remove AddAsync after last-saved-checkin-metadata problem is fixed properly.
public async Task AddAsync(string localPath)
Expand Down
3 changes: 2 additions & 1 deletion src/Agent.Plugins/TfsVCSourceProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ public async Task GetSourceAsync(
if (PlatformUtil.RunningOnWindows)
{
// Set TFVC_BUILDAGENT_POLICYPATH
string policyDllPath = Path.Combine(executionContext.Variables.GetValueOrDefault("Agent.HomeDirectory")?.Value, "externals", "tf", "Microsoft.TeamFoundation.VersionControl.Controls.dll");
string tfDirectoryName = AgentKnobs.InstallLegacyTfExe.GetValue(executionContext).AsBoolean() ? "tf-legacy" : "tf";
string policyDllPath = Path.Combine(executionContext.Variables.GetValueOrDefault("Agent.HomeDirectory")?.Value, "externals", tfDirectoryName, "Microsoft.TeamFoundation.VersionControl.Controls.dll");
ArgUtil.File(policyDllPath, nameof(policyDllPath));
const string policyPathEnvKey = "TFVC_BUILDAGENT_POLICYPATH";
executionContext.Output(StringUtil.Loc("SetEnvVar", policyPathEnvKey));
Expand Down
8 changes: 8 additions & 0 deletions src/Agent.Sdk/Knob/AgentKnobs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -758,5 +758,13 @@ public class AgentKnobs
"Use PowerShell script wrapper to handle PowerShell ConstrainedLanguage mode.",
new PipelineFeatureSource("UsePSScriptWrapper"),
new BuiltInDefaultKnobSource("false"));

public static readonly Knob InstallLegacyTfExe = new Knob(
nameof(InstallLegacyTfExe),
"If true, the agent will install the legacy versions of TF, vstsom and vstshost",
new RuntimeKnobSource("AGENT_INSTALL_LEGACY_TF_EXE"),
new EnvironmentKnobSource("AGENT_INSTALL_LEGACY_TF_EXE"),
new PipelineFeatureSource("InstallLegacyTfExe"),
new BuiltInDefaultKnobSource("false"));
}
}
11 changes: 8 additions & 3 deletions src/Agent.Worker/Build/TFCommandManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.Text;
using System.Xml;
using System.Security.Cryptography.X509Certificates;
using Agent.Sdk.Knob;

namespace Microsoft.VisualStudio.Services.Agent.Worker.Build
{
Expand All @@ -34,11 +35,15 @@ public override TfsVCFeatures Features

protected override string Switch => "/";

public override string FilePath => Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Tf), "tf.exe");
private string TfPath => AgentKnobs.InstallLegacyTfExe.GetValue(ExecutionContext).AsBoolean()
? HostContext.GetDirectory(WellKnownDirectory.TfLegacy)
: HostContext.GetDirectory(WellKnownDirectory.Tf);

private string AppConfigFile => Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Tf), "tf.exe.config");
public override string FilePath => Path.Combine(TfPath, "tf.exe");

private string AppConfigRestoreFile => Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Tf), "tf.exe.config.restore");
private string AppConfigFile => Path.Combine(TfPath, "tf.exe.config");

private string AppConfigRestoreFile => Path.Combine(TfPath, "tf.exe.config.restore");

// TODO: Remove AddAsync after last-saved-checkin-metadata problem is fixed properly.
public async Task AddAsync(string localPath)
Expand Down
7 changes: 6 additions & 1 deletion src/Agent.Worker/Build/TfsVCSourceProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Agent.Sdk.Knob;

namespace Microsoft.VisualStudio.Services.Agent.Worker.Build
{
Expand Down Expand Up @@ -88,7 +89,11 @@ public async Task GetSourceAsync(
if (PlatformUtil.RunningOnWindows)
{
// Set TFVC_BUILDAGENT_POLICYPATH
string policyDllPath = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.ServerOM), "Microsoft.TeamFoundation.VersionControl.Controls.dll");
string vstsomPath = AgentKnobs.InstallLegacyTfExe.GetValue(executionContext).AsBoolean()
? HostContext.GetDirectory(WellKnownDirectory.ServerOMLegacy)
: HostContext.GetDirectory(WellKnownDirectory.ServerOM);

string policyDllPath = Path.Combine(vstsomPath, "Microsoft.TeamFoundation.VersionControl.Controls.dll");
ArgUtil.File(policyDllPath, nameof(policyDllPath));
const string policyPathEnvKey = "TFVC_BUILDAGENT_POLICYPATH";
executionContext.Output(StringUtil.Loc("SetEnvVar", policyPathEnvKey));
Expand Down
34 changes: 28 additions & 6 deletions src/Agent.Worker/Handlers/LegacyPowerShellHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Microsoft.VisualStudio.Services.WebApi;
using System.Xml;
using Microsoft.TeamFoundation.DistributedTask.Pipelines;
using Agent.Sdk.Knob;

namespace Microsoft.VisualStudio.Services.Agent.Worker.Handlers
{
Expand Down Expand Up @@ -81,7 +82,10 @@ private List<Tuple<String, List<Tuple<String, String>>>> GetAdditionalCommandsFo
}

// Initialize our Azure Support (imports the module, sets up the Azure subscription)
string path = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), "vstshost");
string path = AgentKnobs.InstallLegacyTfExe.GetValue(ExecutionContext).AsBoolean()
? Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), "vstshost-legacy")
: Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), "vstshost");

string azurePSM1 = Path.Combine(path, "Microsoft.TeamFoundation.DistributedTask.Task.Deployment.Azure\\Microsoft.TeamFoundation.DistributedTask.Task.Deployment.Azure.psm1");

Trace.Verbose("AzurePowerShellHandler.UpdatePowerShellEnvironment - AddCommand(Import-Module)");
Expand Down Expand Up @@ -205,9 +209,18 @@ public async Task RunAsync()

// Copy the OM binaries into the legacy host folder.
ExecutionContext.Output(StringUtil.Loc("PrepareTaskExecutionHandler"));

string sourceDirectory = AgentKnobs.InstallLegacyTfExe.GetValue(ExecutionContext).AsBoolean()
? HostContext.GetDirectory(WellKnownDirectory.ServerOMLegacy)
: HostContext.GetDirectory(WellKnownDirectory.ServerOM);

string targetDirectory = AgentKnobs.InstallLegacyTfExe.GetValue(ExecutionContext).AsBoolean()
? HostContext.GetDirectory(WellKnownDirectory.LegacyPSHostLegacy)
: HostContext.GetDirectory(WellKnownDirectory.LegacyPSHost);

IOUtil.CopyDirectory(
source: HostContext.GetDirectory(WellKnownDirectory.ServerOM),
target: HostContext.GetDirectory(WellKnownDirectory.LegacyPSHost),
source: sourceDirectory,
target: targetDirectory,
cancellationToken: ExecutionContext.CancellationToken);
Trace.Info("Finished copying files.");

Expand All @@ -230,7 +243,11 @@ public async Task RunAsync()

try
{
String vstsPSHostExe = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.LegacyPSHost), "LegacyVSTSPowerShellHost.exe");
String vstsPSHostExeDirectory = AgentKnobs.InstallLegacyTfExe.GetValue(ExecutionContext).AsBoolean()
? HostContext.GetDirectory(WellKnownDirectory.LegacyPSHostLegacy)
: HostContext.GetDirectory(WellKnownDirectory.LegacyPSHost);

String vstsPSHostExe = Path.Combine(vstsPSHostExeDirectory, "LegacyVSTSPowerShellHost.exe");
Int32 exitCode = await processInvoker.ExecuteAsync(workingDirectory: workingDirectory,
fileName: vstsPSHostExe,
arguments: "",
Expand Down Expand Up @@ -432,10 +449,15 @@ protected virtual void AddLegacyHostEnvironmentVariables(string scriptFile, stri

private void AddProxySetting(IVstsAgentWebProxy agentProxy)
{
string appConfig = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.LegacyPSHost), _appConfigFileName);
string psHostDirectory = AgentKnobs.InstallLegacyTfExe.GetValue(ExecutionContext).AsBoolean()
? HostContext.GetDirectory(WellKnownDirectory.LegacyPSHostLegacy)
: HostContext.GetDirectory(WellKnownDirectory.LegacyPSHost);

string appConfig = Path.Combine(psHostDirectory, _appConfigFileName);

ArgUtil.File(appConfig, _appConfigFileName);

string appConfigRestore = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.LegacyPSHost), _appConfigRestoreFileName);
string appConfigRestore = Path.Combine(psHostDirectory, _appConfigRestoreFileName);
if (!File.Exists(appConfigRestore))
{
Trace.Info("Take snapshot of current appconfig for restore modified appconfig.");
Expand Down
5 changes: 5 additions & 0 deletions src/Agent.Worker/JobExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,11 @@ public async Task<List<IStep>> InitializeJob(IExecutionContext jobContext, Pipel
}
}

if (AgentKnobs.InstallLegacyTfExe.GetValue(jobContext).AsBoolean())
{
await TfManager.DownloadLegacyTfToolsAsync(context);
}

// build up 3 lists of steps, pre-job, job, post-job
Stack<IStep> postJobStepsBuilder = new Stack<IStep>();
Dictionary<Guid, Variables> taskVariablesMapping = new Dictionary<Guid, Variables>();
Expand Down
6 changes: 5 additions & 1 deletion src/Agent.Worker/JobRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,11 @@ public async Task<TaskResult> RunAsync(Pipelines.AgentJobRequestMessage message,
jobContext.SetVariable(Constants.Variables.Agent.RootDirectory, HostContext.GetDirectory(WellKnownDirectory.Work), isFilePath: true);
if (PlatformUtil.RunningOnWindows)
{
jobContext.SetVariable(Constants.Variables.Agent.ServerOMDirectory, HostContext.GetDirectory(WellKnownDirectory.ServerOM), isFilePath: true);
string serverOMDirectoryVariable = AgentKnobs.InstallLegacyTfExe.GetValue(jobContext).AsBoolean()
? HostContext.GetDirectory(WellKnownDirectory.ServerOMLegacy)
: HostContext.GetDirectory(WellKnownDirectory.ServerOM);

jobContext.SetVariable(Constants.Variables.Agent.ServerOMDirectory, serverOMDirectoryVariable, isFilePath: true);
}
if (!PlatformUtil.RunningOnWindows)
{
Expand Down
5 changes: 5 additions & 0 deletions src/Agent.Worker/Release/ReleaseJobExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@ private async Task DownloadArtifacts(IExecutionContext executionContext,
await teeUtil.DownloadTeeIfAbsent();
}

if (AgentKnobs.InstallLegacyTfExe.GetValue(executionContext).AsBoolean())
{
await TfManager.DownloadLegacyTfToolsAsync(executionContext);
}

try
{
foreach (AgentArtifactDefinition agentArtifactDefinition in agentArtifactDefinitions)
Expand Down
145 changes: 145 additions & 0 deletions src/Agent.Worker/TfManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
using Microsoft.VisualStudio.Services.Agent.Util;
using Microsoft.VisualStudio.Services.Agent.Worker;
using System;
using System.IO;
using System.IO.Compression;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.VisualStudio.Services.Agent.Worker
{
public interface IRetryOptions
{
int CurrentCount { get; set; }
int Limit { get; init; }
}

public record RetryOptions : IRetryOptions
{
public int CurrentCount { get; set; }
public int Limit { get; init; }
}

public static class TfManager
{
public static async Task DownloadLegacyTfToolsAsync(IExecutionContext executionContext)
{
ArgUtil.NotNull(executionContext, nameof(executionContext));
string externalsPath = Path.Combine(executionContext.GetVariableValueOrDefault("Agent.HomeDirectory"), Constants.Path.ExternalsDirectory);
ArgUtil.NotNull(externalsPath, nameof(externalsPath));

string tfLegacyExternalsPath = Path.Combine(externalsPath, "tf-legacy");
var retryOptions = new RetryOptions() { CurrentCount = 0, Limit = 3 };

if (!Directory.Exists(tfLegacyExternalsPath))
{
const string tfDownloadUrl = "https://vstsagenttools.blob.core.windows.net/tools/vstsom/m153_47c0856d/vstsom.zip";
string tempTfDirectory = Path.Combine(externalsPath, "tf_download_temp");

await DownloadAsync(executionContext, tfDownloadUrl, tempTfDirectory, tfLegacyExternalsPath, retryOptions);
}
else
{
executionContext.Debug($"tf-legacy download already exists at {tfLegacyExternalsPath}.");
}

string vstsomLegacyExternalsPath = Path.Combine(externalsPath, "vstsom-legacy");

if (!Directory.Exists(vstsomLegacyExternalsPath))
{
const string vstsomDownloadUrl = "https://vstsagenttools.blob.core.windows.net/tools/vstsom/m122_887c6659/vstsom.zip";
string tempVstsomDirectory = Path.Combine(externalsPath, "vstsom_download_temp");

await DownloadAsync(executionContext, vstsomDownloadUrl, tempVstsomDirectory, vstsomLegacyExternalsPath, retryOptions);
}
else
{
executionContext.Debug($"vstsom-legacy download already exists at {vstsomLegacyExternalsPath}.");
}

string vstsHostLegacyExternalsPath = Path.Combine(externalsPath, "vstshost-legacy");

if (!Directory.Exists(vstsHostLegacyExternalsPath))
{
const string vstsHostDownloadUrl = "https://vstsagenttools.blob.core.windows.net/tools/vstshost/m122_887c6659/vstshost.zip";
string tempVstsHostDirectory = Path.Combine(externalsPath, "vstshost_download_temp");

await DownloadAsync(executionContext, vstsHostDownloadUrl, tempVstsHostDirectory, vstsHostLegacyExternalsPath, retryOptions);
}
else
{
executionContext.Debug($"vstshost-legacy download already exists at {vstsHostLegacyExternalsPath}.");
}
}

public static async Task DownloadAsync(IExecutionContext executionContext, string blobUrl, string tempDirectory, string extractPath, IRetryOptions retryOptions)
{
Directory.CreateDirectory(tempDirectory);
string downloadPath = Path.ChangeExtension(Path.Combine(tempDirectory, "download"), ".completed");
string toolName = new DirectoryInfo(extractPath).Name;

const int timeout = 180;
const int defaultFileStreamBufferSize = 4096;
const int retryDelay = 10000;

try
{
using CancellationTokenSource downloadCts = new(TimeSpan.FromSeconds(timeout));
using CancellationTokenSource linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(downloadCts.Token, executionContext.CancellationToken);
CancellationToken cancellationToken = linkedTokenSource.Token;

using HttpClient httpClient = new();
using Stream stream = await httpClient.GetStreamAsync(blobUrl, cancellationToken);
using FileStream fs = new(downloadPath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: defaultFileStreamBufferSize, useAsync: true);

while (retryOptions.CurrentCount < retryOptions.Limit)
{
try
{
executionContext.Debug($"Retry options: {retryOptions.ToString()}.");
await stream.CopyToAsync(fs, cancellationToken);
executionContext.Debug($"Finished downloading {toolName}.");
await fs.FlushAsync(cancellationToken);
fs.Close();
break;
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
executionContext.Debug($"{toolName} download has been cancelled.");
throw;
}
catch (Exception)
{
retryOptions.CurrentCount++;

if (retryOptions.CurrentCount == retryOptions.Limit)
{
IOUtil.DeleteDirectory(tempDirectory, CancellationToken.None);
executionContext.Error($"Retry limit for {toolName} download has been exceeded.");
return;
}

executionContext.Debug($"Failed to download {toolName}");
executionContext.Debug($"Retry {toolName} download in 10 seconds.");
await Task.Delay(retryDelay, cancellationToken);
}
}

executionContext.Debug($"Extracting {toolName}...");
ZipFile.ExtractToDirectory(downloadPath, extractPath);
File.WriteAllText(downloadPath, DateTime.UtcNow.ToString());
executionContext.Debug($"{toolName} has been extracted and cleaned up");
}
catch (Exception ex)
{
executionContext.Error(ex);
}
finally
{
IOUtil.DeleteDirectory(tempDirectory, CancellationToken.None);
executionContext.Debug($"{toolName} download directory has been cleaned up.");
}
}
}
}
6 changes: 6 additions & 0 deletions src/Microsoft.VisualStudio.Services.Agent/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ public enum WellKnownDirectory
Tools,
Update,
Work,
TfLegacy,
ServerOMLegacy,
LegacyPSHostLegacy
}

public enum WellKnownConfigFile
Expand Down Expand Up @@ -312,10 +315,13 @@ public static class Path
public static readonly string DiagDirectory = "_diag";
public static readonly string ExternalsDirectory = "externals";
public static readonly string LegacyPSHostDirectory = "vstshost";
public static readonly string LegacyPSHostLegacyDirectory = "vstshost-legacy";
public static readonly string ServerOMDirectory = "vstsom";
public static readonly string ServerOMLegacyDirectory = "vstsom-legacy";
public static readonly string TempDirectory = "_temp";
public static readonly string TeeDirectory = "tee";
public static readonly string TfDirectory = "tf";
public static readonly string TfLegacyDirectory = "tf-legacy";
public static readonly string ToolDirectory = "_tool";
public static readonly string TaskJsonFile = "task.json";
public static readonly string TasksDirectory = "_tasks";
Expand Down
Loading
Loading