From 3d3822c3062a544b7d4387e69519771b2b23e441 Mon Sep 17 00:00:00 2001 From: Ivan Duplenskikh <115665590+ivanduplenskikh@users.noreply.github.com> Date: Tue, 17 Sep 2024 13:43:48 +0200 Subject: [PATCH] Add the credential environment configuration to the Git checkout command (#4965) * Add the credential environment configuration to the Git checkout command The credential environment config property is added to the Git checkout command when either filter fetch options or the agent knob ADD_FORCE_CREDENTIALS_TO_GIT_CHECKOUT is provided * Add the argument to add when both conditions are met * Refactor GitCliManager and GitSourceProvider - Variables are now declared using the var keyword - The GitFetch method now accepts an IEnumerable for the filters parameter instead of a List --- src/Agent.Plugins/GitCliManager.cs | 50 ++---------------- src/Agent.Plugins/GitSourceProvider.cs | 70 ++++++++++++++++++++++++-- src/Agent.Sdk/Knob/AgentKnobs.cs | 7 +++ 3 files changed, 76 insertions(+), 51 deletions(-) diff --git a/src/Agent.Plugins/GitCliManager.cs b/src/Agent.Plugins/GitCliManager.cs index 3043fc077e..258d820764 100644 --- a/src/Agent.Plugins/GitCliManager.cs +++ b/src/Agent.Plugins/GitCliManager.cs @@ -195,7 +195,7 @@ public async Task GitInit(AgentTaskPluginExecutionContext context, string r } // git fetch --tags --prune --progress --no-recurse-submodules [--depth=15] origin [+refs/pull/*:refs/remote/pull/*] - public async Task GitFetch(AgentTaskPluginExecutionContext context, string repositoryPath, string remoteName, int fetchDepth, string fetchFilter, bool fetchTags, List refSpec, string additionalCommandLine, CancellationToken cancellationToken) + public async Task GitFetch(AgentTaskPluginExecutionContext context, string repositoryPath, string remoteName, int fetchDepth, IEnumerable filters, bool fetchTags, List refSpec, string additionalCommandLine, CancellationToken cancellationToken) { context.Debug($"Fetch git repository at: {repositoryPath} remote: {remoteName}."); if (refSpec != null && refSpec.Count > 0) @@ -233,50 +233,8 @@ public async Task GitFetch(AgentTaskPluginExecutionContext context, string // add --unshallow to convert the shallow repository to a complete repository string depth = fetchDepth > 0 ? $"--depth={fetchDepth}" : (File.Exists(Path.Combine(repositoryPath, ".git", "shallow")) ? "--unshallow" : string.Empty); - // parse filter and only include valid options - List filters = new List(); - - if (AgentKnobs.UseFetchFilterInCheckoutTask.GetValue(context).AsBoolean()) - { - List splitFilter = fetchFilter.Split('+').Where(filter => !String.IsNullOrWhiteSpace(filter)).ToList(); - - foreach (string filter in splitFilter) - { - List parsedFilter = filter.Split(':') - .Where(filter => !String.IsNullOrWhiteSpace(filter)) - .Select(filter => filter.Trim()) - .ToList(); - - if (parsedFilter.Count == 2) - { - switch (parsedFilter[0].ToLower()) - { - case "tree": - // currently only supporting treeless filter - if (int.TryParse(parsedFilter[1], out int treeSize) && treeSize == 0) - { - filters.Add($"{parsedFilter[0]}:{treeSize}"); - } - break; - - case "blob": - // currently only supporting blobless filter - if (parsedFilter[1].Equals("none", StringComparison.OrdinalIgnoreCase)) - { - filters.Add($"{parsedFilter[0]}:{parsedFilter[1]}"); - } - break; - - default: - // either invalid or unsupported git object - break; - } - } - } - } - //define options for fetch - string options = $"{forceTag} {tags} --prune {pruneTags} {progress} --no-recurse-submodules {remoteName} {depth} {String.Join(" ", filters.Select(filter => "--filter=" + filter))} {string.Join(" ", refSpec)}"; + string options = $"{forceTag} {tags} --prune {pruneTags} {progress} --no-recurse-submodules {remoteName} {depth} {string.Join(" ", filters.Select(filter => "--filter=" + filter))} {string.Join(" ", refSpec)}"; int retryCount = 0; int fetchExitCode = 0; while (retryCount < 3) @@ -363,7 +321,7 @@ public async Task GitLFSFetch(AgentTaskPluginExecutionContext context, stri } // git checkout -f --progress - public async Task GitCheckout(AgentTaskPluginExecutionContext context, string repositoryPath, string committishOrBranchSpec, CancellationToken cancellationToken) + public async Task GitCheckout(AgentTaskPluginExecutionContext context, string repositoryPath, string committishOrBranchSpec, string additionalCommandLine, CancellationToken cancellationToken) { context.Debug($"Checkout {committishOrBranchSpec}."); @@ -378,7 +336,7 @@ public async Task GitCheckout(AgentTaskPluginExecutionContext context, stri options = StringUtil.Format("--force {0}", committishOrBranchSpec); } - return await ExecuteGitCommandAsync(context, repositoryPath, "checkout", options, cancellationToken); + return await ExecuteGitCommandAsync(context, repositoryPath, "checkout", options, additionalCommandLine, cancellationToken); } // git clean -ffdx diff --git a/src/Agent.Plugins/GitSourceProvider.cs b/src/Agent.Plugins/GitSourceProvider.cs index f44b0870ea..9e7123fe57 100644 --- a/src/Agent.Plugins/GitSourceProvider.cs +++ b/src/Agent.Plugins/GitSourceProvider.cs @@ -718,8 +718,10 @@ public async Task GetSourceAsync( await RemoveGitConfig(executionContext, gitCommandManager, targetPath, $"http.proxy", string.Empty); } - List additionalFetchArgs = new List(); - List additionalLfsFetchArgs = new List(); + var additionalFetchFilterOptions = ParseFetchFilterOptions(executionContext, fetchFilter); + var additionalFetchArgs = new List(); + var additionalLfsFetchArgs = new List(); + var additionalCheckoutArgs = new List(); // Force Git to HTTP/1.1. Otherwise IIS will reject large pushes to Azure Repos due to the large content-length header // This is caused by these header limits - https://docs.microsoft.com/en-us/iis/configuration/system.webserver/security/requestfiltering/requestlimits/headerlimits/ @@ -738,6 +740,11 @@ public async Task GetSourceAsync( string configKey = "http.extraheader"; string args = ComposeGitArgs(executionContext, gitCommandManager, configKey, username, password, useBearerAuthType); additionalFetchArgs.Add(args); + + if (additionalFetchFilterOptions.Any() && AgentKnobs.AddForceCredentialsToGitCheckout.GetValue(executionContext).AsBoolean()) + { + additionalCheckoutArgs.Add(args); + } } else { @@ -899,7 +906,7 @@ public async Task GetSourceAsync( } } - int exitCode_fetch = await gitCommandManager.GitFetch(executionContext, targetPath, "origin", fetchDepth, fetchFilter, fetchTags, additionalFetchSpecs, string.Join(" ", additionalFetchArgs), cancellationToken); + int exitCode_fetch = await gitCommandManager.GitFetch(executionContext, targetPath, "origin", fetchDepth, additionalFetchFilterOptions, fetchTags, additionalFetchSpecs, string.Join(" ", additionalFetchArgs), cancellationToken); if (exitCode_fetch != 0) { throw new InvalidOperationException($"Git fetch failed with exit code: {exitCode_fetch}"); @@ -911,7 +918,7 @@ public async Task GetSourceAsync( if (fetchByCommit && !string.IsNullOrEmpty(sourceVersion)) { List commitFetchSpecs = new List() { $"+{sourceVersion}" }; - exitCode_fetch = await gitCommandManager.GitFetch(executionContext, targetPath, "origin", fetchDepth, fetchFilter, fetchTags, commitFetchSpecs, string.Join(" ", additionalFetchArgs), cancellationToken); + exitCode_fetch = await gitCommandManager.GitFetch(executionContext, targetPath, "origin", fetchDepth, additionalFetchFilterOptions, fetchTags, commitFetchSpecs, string.Join(" ", additionalFetchArgs), cancellationToken); if (exitCode_fetch != 0) { throw new InvalidOperationException($"Git fetch failed with exit code: {exitCode_fetch}"); @@ -963,7 +970,7 @@ public async Task GetSourceAsync( } // Finally, checkout the sourcesToBuild (if we didn't find a valid git object this will throw) - int exitCode_checkout = await gitCommandManager.GitCheckout(executionContext, targetPath, sourcesToBuild, cancellationToken); + int exitCode_checkout = await gitCommandManager.GitCheckout(executionContext, targetPath, sourcesToBuild, string.Join(" ", additionalCheckoutArgs), cancellationToken); if (exitCode_checkout != 0) { // local repository is shallow repository, checkout may fail due to lack of commits history. @@ -1374,6 +1381,59 @@ private async Task IsRepositoryOriginUrlMatch(AgentTaskPluginExecutionCont } } + private IEnumerable ParseFetchFilterOptions(AgentTaskPluginExecutionContext context, string fetchFilter) + { + if (!AgentKnobs.UseFetchFilterInCheckoutTask.GetValue(context).AsBoolean()) + { + return Enumerable.Empty(); + } + + if (string.IsNullOrEmpty(fetchFilter)) + { + return Enumerable.Empty(); + } + + // parse filter and only include valid options + var filters = new List(); + var splitFilter = fetchFilter.Split('+').Where(filter => !string.IsNullOrWhiteSpace(filter)).ToList(); + + foreach (string filter in splitFilter) + { + var parsedFilter = filter.Split(':') + .Where(filter => !string.IsNullOrWhiteSpace(filter)) + .Select(filter => filter.Trim()) + .ToList(); + + if (parsedFilter.Count == 2) + { + switch (parsedFilter[0].ToLower()) + { + case "tree": + // currently only supporting treeless filter + if (int.TryParse(parsedFilter[1], out int treeSize) && treeSize == 0) + { + filters.Add($"{parsedFilter[0]}:{treeSize}"); + } + break; + + case "blob": + // currently only supporting blobless filter + if (parsedFilter[1].Equals("none", StringComparison.OrdinalIgnoreCase)) + { + filters.Add($"{parsedFilter[0]}:{parsedFilter[1]}"); + } + break; + + default: + // either invalid or unsupported git object + break; + } + } + } + + return filters; + } + private async Task RunGitStatusIfSystemDebug(AgentTaskPluginExecutionContext executionContext, GitCliManager gitCommandManager, string targetPath) { if (executionContext.IsSystemDebugTrue()) diff --git a/src/Agent.Sdk/Knob/AgentKnobs.cs b/src/Agent.Sdk/Knob/AgentKnobs.cs index 884bf820b2..5a188dde27 100644 --- a/src/Agent.Sdk/Knob/AgentKnobs.cs +++ b/src/Agent.Sdk/Knob/AgentKnobs.cs @@ -759,6 +759,13 @@ public class AgentKnobs new PipelineFeatureSource("UsePSScriptWrapper"), new BuiltInDefaultKnobSource("false")); + public static readonly Knob AddForceCredentialsToGitCheckout = new Knob( + nameof(AddForceCredentialsToGitCheckout), + "If true, the credentials will be forcibly added to the Git checkout command.", + new RuntimeKnobSource("ADD_FORCE_CREDENTIALS_TO_GIT_CHECKOUT"), + new PipelineFeatureSource(nameof(AddForceCredentialsToGitCheckout)), + 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",