Skip to content

Commit

Permalink
Update tf.exe (#4955)
Browse files Browse the repository at this point in the history
* WIP

* Refactoring

* Refactor tool download code

* Refactor + Add tests

* Fix test

* Change RetryOptions to record to be able to use default ToString override for logging

---------

Co-authored-by: v-levockina <undefined>
Co-authored-by: Kirill Ivlev <102740624+kirill-ivlev@users.noreply.github.com>
  • Loading branch information
aleksandrlevochkin and kirill-ivlev committed Sep 3, 2024
1 parent e44cd18 commit 39c297f
Show file tree
Hide file tree
Showing 16 changed files with 334 additions and 13 deletions.
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))
{
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:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Externals),
Constants.Path.TfLegacyDirectory);
break;

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

0 comments on commit 39c297f

Please sign in to comment.