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 #4955

Merged
merged 10 commits into from
Sep 3, 2024
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
7 changes: 7 additions & 0 deletions src/Agent.Sdk/Knob/AgentKnobs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -758,5 +758,12 @@ 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, agent will install the previous version of TF.exe in the tf-legacy and vstsom-legacy directories",
new RuntimeKnobSource("AGENT_INSTALL_LEGACY_TF_EXE"),
new EnvironmentKnobSource("AGENT_INSTALL_LEGACY_TF_EXE"),
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
8 changes: 7 additions & 1 deletion 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 @@ -205,8 +206,13 @@ 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);

IOUtil.CopyDirectory(
source: HostContext.GetDirectory(WellKnownDirectory.ServerOM),
source: sourceDirectory,
target: HostContext.GetDirectory(WellKnownDirectory.LegacyPSHost),
cancellationToken: ExecutionContext.CancellationToken);
Trace.Info("Finished copying files.");
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 @@ -268,6 +268,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
130 changes: 130 additions & 0 deletions src/Agent.Worker/TfManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using Microsoft.VisualStudio.Services.Agent.Util;
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))
DergachevE marked this conversation as resolved.
Show resolved Hide resolved
{
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}.");
}
}

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.");
}
}
}
}
4 changes: 4 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,8 @@ public enum WellKnownDirectory
Tools,
Update,
Work,
TfLegacy,
ServerOMLegacy
}

public enum WellKnownConfigFile
Expand Down Expand Up @@ -313,9 +315,11 @@ public static class Path
public static readonly string ExternalsDirectory = "externals";
public static readonly string LegacyPSHostDirectory = "vstshost";
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
12 changes: 12 additions & 0 deletions src/Microsoft.VisualStudio.Services.Agent/HostContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,12 +206,24 @@ public virtual string GetDirectory(WellKnownDirectory directory)
Constants.Path.ServerOMDirectory);
break;

case WellKnownDirectory.ServerOMLegacy:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Externals),
Constants.Path.ServerOMLegacyDirectory);
break;

case WellKnownDirectory.Tf:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Externals),
Constants.Path.TfDirectory);
break;

case WellKnownDirectory.TfLegacy:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Externals),
Constants.Path.TfLegacyDirectory);
break;

case WellKnownDirectory.Tee:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Externals),
Expand Down
4 changes: 2 additions & 2 deletions src/Misc/externals.sh
Original file line number Diff line number Diff line change
Expand Up @@ -164,14 +164,14 @@ if [[ "$PACKAGERUNTIME" == "win-x"* ]]; then

acquireExternalTool "$CONTAINER_URL/azcopy/1/azcopy.zip" azcopy
acquireExternalTool "$CONTAINER_URL/vstshost/m122_887c6659/vstshost.zip" vstshost
acquireExternalTool "$CONTAINER_URL/vstsom/m122_887c6659/vstsom.zip" vstsom
acquireExternalTool "$CONTAINER_URL/vstsom/m153_47c0856d_adhoc/vstsom.zip" vstsom
fi

acquireExternalTool "$CONTAINER_URL/mingit/${MINGIT_VERSION}/MinGit-${MINGIT_VERSION}-${BIT}-bit.zip" git
acquireExternalTool "$CONTAINER_URL/git-lfs/${LFS_VERSION}/x${BIT}/git-lfs.exe" "git/mingw${BIT}/bin"
acquireExternalTool "$CONTAINER_URL/pdbstr/1/pdbstr.zip" pdbstr
acquireExternalTool "$CONTAINER_URL/symstore/1/symstore.zip" symstore
acquireExternalTool "$CONTAINER_URL/vstsom/m153_47c0856d/vstsom.zip" tf
acquireExternalTool "$CONTAINER_URL/vstsom/m153_47c0856d_adhoc/vstsom.zip" tf
acquireExternalTool "$CONTAINER_URL/vswhere/2_8_4/vswhere.zip" vswhere
acquireExternalTool "https://dist.nuget.org/win-x86-commandline/v3.4.4/nuget.exe" nuget

Expand Down
3 changes: 2 additions & 1 deletion src/Test/L0/ServiceInterfacesL0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ public void WorkerInterfacesSpecifyDefaultImplementation()
typeof(IResultReader),
typeof(INUnitResultsXmlReader),
typeof(IWorkerCommand),
typeof(ITaskRestrictionsChecker)
typeof(ITaskRestrictionsChecker),
typeof(IRetryOptions)
};
Validate(
assembly: typeof(IStepsRunner).GetTypeInfo().Assembly,
Expand Down
12 changes: 12 additions & 0 deletions src/Test/L0/TestHostContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,12 +211,24 @@ public string GetDirectory(WellKnownDirectory directory)
Constants.Path.ServerOMDirectory);
break;

case WellKnownDirectory.ServerOMLegacy:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Externals),
Constants.Path.ServerOMLegacyDirectory);
break;

case WellKnownDirectory.Tf:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Externals),
Constants.Path.TfDirectory);
break;

case WellKnownDirectory.TfLegacy:
kirill-ivlev marked this conversation as resolved.
Show resolved Hide resolved
path = Path.Combine(
GetDirectory(WellKnownDirectory.Externals),
Constants.Path.TfLegacyDirectory);
break;

case WellKnownDirectory.Tee:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Externals),
Expand Down
Loading
Loading