public async Task RunPluginTaskAsync(IExecutionContext context, string plugin, Dictionary <string, string> inputs, Dictionary <string, string> environment, Variables runtimeVariables, EventHandler <ProcessDataReceivedEventArgs> outputHandler) { ArgUtil.NotNullOrEmpty(plugin, nameof(plugin)); // Only allow plugins we defined if (!_taskPlugins.Contains(plugin)) { throw new NotSupportedException(plugin); } // Resolve the working directory. string workingDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); ArgUtil.Directory(workingDirectory, nameof(workingDirectory)); // Agent.PluginHost string file = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), $"Agent.PluginHost{Util.IOUtil.ExeExtension}"); ArgUtil.File(file, $"Agent.PluginHost{Util.IOUtil.ExeExtension}"); // Agent.PluginHost's arguments string arguments = $"task \"{plugin}\""; // construct plugin context var target = context.StepTarget(); AgentTaskPluginExecutionContext pluginContext = new AgentTaskPluginExecutionContext { Inputs = inputs, Repositories = context.Repositories, Endpoints = context.Endpoints, Container = target is ContainerInfo ? target as ContainerInfo : null, //TODO: Figure out if this needs to have all the containers or just the one for the current step JobSettings = context.JobSettings, }; // variables runtimeVariables.CopyInto(pluginContext.Variables); context.TaskVariables.CopyInto(pluginContext.TaskVariables); using (var processInvoker = HostContext.CreateService <IProcessInvoker>()) { var redirectStandardIn = new InputQueue <string>(); redirectStandardIn.Enqueue(JsonUtility.ToString(pluginContext)); processInvoker.OutputDataReceived += outputHandler; processInvoker.ErrorDataReceived += outputHandler; // Execute the process. Exit code 0 should always be returned. // A non-zero exit code indicates infrastructural failure. // Task failure should be communicated over STDOUT using ## commands. await processInvoker.ExecuteAsync(workingDirectory : workingDirectory, fileName : file, arguments : arguments, environment : environment, requireExitCodeZero : true, outputEncoding : Encoding.UTF8, killProcessOnCancel : false, redirectStandardIn : redirectStandardIn, cancellationToken : context.CancellationToken); } }
// git lfs version public async Task <Version> GitLfsVersion(AgentTaskPluginExecutionContext context) { context.Debug("Get git-lfs version."); string workingDir = context.Variables.GetValueOrDefault("agent.workfolder")?.Value; ArgUtil.Directory(workingDir, "agent.workfolder"); Version version = null; List <string> outputStrings = new List <string>(); int exitCode = await ExecuteGitCommandAsync(context, workingDir, "lfs version", null, outputStrings); context.Output($"{string.Join(Environment.NewLine, outputStrings)}"); if (exitCode == 0) { // remove any empty line. outputStrings = outputStrings.Where(o => !string.IsNullOrEmpty(o)).ToList(); if (outputStrings.Count == 1 && !string.IsNullOrEmpty(outputStrings.First())) { string verString = outputStrings.First(); // we interested about major.minor.patch version Regex verRegex = new Regex("\\d+\\.\\d+(\\.\\d+)?", RegexOptions.IgnoreCase); var matchResult = verRegex.Match(verString); if (matchResult.Success && !string.IsNullOrEmpty(matchResult.Value)) { if (!Version.TryParse(matchResult.Value, out version)) { version = null; } } } } return(version); }
// git version public async Task <Version> GitVersion(RunnerActionPluginExecutionContext context) { context.Debug("Get git version."); string runnerWorkspace = context.GetRunnerContext("workspace"); ArgUtil.Directory(runnerWorkspace, "runnerWorkspace"); Version version = null; List <string> outputStrings = new List <string>(); int exitCode = await ExecuteGitCommandAsync(context, runnerWorkspace, "version", null, outputStrings); context.Output($"{string.Join(Environment.NewLine, outputStrings)}"); if (exitCode == 0) { // remove any empty line. outputStrings = outputStrings.Where(o => !string.IsNullOrEmpty(o)).ToList(); if (outputStrings.Count == 1 && !string.IsNullOrEmpty(outputStrings.First())) { string verString = outputStrings.First(); // we interested about major.minor.patch version Regex verRegex = new Regex("\\d+\\.\\d+(\\.\\d+)?", RegexOptions.IgnoreCase); var matchResult = verRegex.Match(verString); if (matchResult.Success && !string.IsNullOrEmpty(matchResult.Value)) { if (!Version.TryParse(matchResult.Value, out version)) { version = null; } } } } return(version); }
public async Task RunAsync() { // Validate args. Trace.Entering(); ArgUtil.NotNull(Data, nameof(Data)); ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext)); ArgUtil.NotNull(Inputs, nameof(Inputs)); ArgUtil.Directory(TaskDirectory, nameof(TaskDirectory)); // Update the env dictionary. AddInputsToEnvironment(); AddEndpointsToEnvironment(); AddSecureFilesToEnvironment(); AddVariablesToEnvironment(); AddTaskVariablesToEnvironment(); AddPrependPathToEnvironment(); // Resolve the target script. ArgUtil.NotNullOrEmpty(Data.Target, nameof(Data.Target)); string scriptFile = Path.Combine(TaskDirectory, Data.Target); ArgUtil.File(scriptFile, nameof(scriptFile)); // Resolve the VSTS Task SDK module definition. string scriptDirectory = Path.GetDirectoryName(scriptFile); string moduleFile = Path.Combine(scriptDirectory, @"ps_modules", "VstsTaskSdk", "VstsTaskSdk.psd1"); ArgUtil.File(moduleFile, nameof(moduleFile)); // Craft the args to pass to PowerShell.exe. string powerShellExeArgs = StringUtil.Format( @"-NoLogo -Sta -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command "". ([scriptblock]::Create('if (!$PSHOME) {{ $null = Get-Item -LiteralPath ''variable:PSHOME'' }} else {{ Import-Module -Name ([System.IO.Path]::Combine($PSHOME, ''Modules\Microsoft.PowerShell.Management\Microsoft.PowerShell.Management.psd1'')) ; Import-Module -Name ([System.IO.Path]::Combine($PSHOME, ''Modules\Microsoft.PowerShell.Utility\Microsoft.PowerShell.Utility.psd1'')) }}')) 2>&1 | ForEach-Object {{ Write-Verbose $_.Exception.Message -Verbose }} ; Import-Module -Name '{0}' -ArgumentList @{{ NonInteractive = $true }} -ErrorAction Stop ; $VerbosePreference = '{1}' ; $DebugPreference = '{1}' ; Invoke-VstsTaskScript -ScriptBlock ([scriptblock]::Create('. ''{2}'''))""", moduleFile.Replace("'", "''"), // nested within a single-quoted string ExecutionContext.Variables.System_Debug == true ? "Continue" : "SilentlyContinue", scriptFile.Replace("'", "''''")); // nested within a single-quoted string within a single-quoted string // Resolve powershell.exe. string powerShellExe = HostContext.GetService <IPowerShellExeUtil>().GetPath(); ArgUtil.NotNullOrEmpty(powerShellExe, nameof(powerShellExe)); // Invoke the process. using (var processInvoker = HostContext.CreateService <IProcessInvoker>()) { processInvoker.OutputDataReceived += OnDataReceived; processInvoker.ErrorDataReceived += OnDataReceived; // Execute the process. Exit code 0 should always be returned. // A non-zero exit code indicates infrastructural failure. // Task failure should be communicated over STDOUT using ## commands. await processInvoker.ExecuteAsync(workingDirectory : scriptDirectory, fileName : powerShellExe, arguments : powerShellExeArgs, environment : Environment, requireExitCodeZero : true, outputEncoding : null, killProcessOnCancel : false, cancellationToken : ExecutionContext.CancellationToken); } }
public async Task RunAsync() { // Validate args. Trace.Entering(); ArgUtil.NotNull(Data, nameof(Data)); ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext)); ArgUtil.NotNull(Inputs, nameof(Inputs)); ArgUtil.Directory(TaskDirectory, nameof(TaskDirectory)); // Update the env dictionary. AddInputsToEnvironment(); AddEndpointsToEnvironment(); AddVariablesToEnvironment(); // Resolve the target script. string target = Data.Target; ArgUtil.NotNullOrEmpty(target, nameof(target)); target = Path.Combine(TaskDirectory, target); ArgUtil.File(target, nameof(target)); // Resolve the working directory. string workingDirectory = Data.WorkingDirectory; if (string.IsNullOrEmpty(workingDirectory)) { workingDirectory = TaskDirectory; } ArgUtil.Directory(workingDirectory, nameof(workingDirectory)); // Setup the process invoker. using (var processInvoker = HostContext.CreateService <IProcessInvoker>()) { processInvoker.OutputDataReceived += OnDataReceived; processInvoker.ErrorDataReceived += OnDataReceived; string node = Path.Combine( IOUtil.GetExternalsPath(), "node", "bin", $"node{IOUtil.ExeExtension}"); // Format the arguments passed to node. // 1) Wrap the script file path in double quotes. // 2) Escape double quotes within the script file path. Double-quote is a valid // file name character on Linux. string arguments = StringUtil.Format(@"""{0}""", target.Replace(@"""", @"\""")); // Execute the process. Exit code 0 should always be returned. // A non-zero exit code indicates infrastructural failure. // Task failure should be communicated over STDOUT using ## commands. await processInvoker.ExecuteAsync( workingDirectory : workingDirectory, fileName : node, arguments : arguments, environment : Environment, requireExitCodeZero : true, cancellationToken : ExecutionContext.CancellationToken); } }
public static string GetSrcPath() { string srcDir = Environment.GetEnvironmentVariable("GITHUB_RUNNER_SRC_DIR"); ArgUtil.Directory(srcDir, nameof(srcDir)); Assert.Equal(Src, Path.GetFileName(srcDir)); return(srcDir); }
public static string GetSrcPath() { string L0dir = Path.GetDirectoryName(GetThisFilePath()); string testDir = Path.GetDirectoryName(L0dir); string srcDir = Path.GetDirectoryName(testDir); ArgUtil.Directory(srcDir, nameof(srcDir)); Assert.Equal(Src, Path.GetFileName(srcDir)); return(srcDir); }
public Task GetSourceAsync( IExecutionContext executionContext, ServiceEndpoint endpoint, CancellationToken cancellationToken) { Trace.Entering(); ArgUtil.Equal(RunMode.Local, HostContext.RunMode, nameof(HostContext.RunMode)); ArgUtil.Equal(HostTypes.Build, executionContext.Variables.System_HostType, nameof(executionContext.Variables.System_HostType)); ArgUtil.NotNull(executionContext, nameof(executionContext)); ArgUtil.NotNull(endpoint, nameof(endpoint)); bool preferGitFromPath; #if OS_WINDOWS bool overrideGitFromPath; bool.TryParse(Environment.GetEnvironmentVariable(Constants.Variables.System.PreferGitFromPath), out overrideGitFromPath); preferGitFromPath = overrideGitFromPath || (executionContext.Variables.GetBoolean(Constants.Variables.System.PreferGitFromPath) ?? false); #else preferGitFromPath = true; #endif if (!preferGitFromPath) { // Add git to the PATH. string gitPath = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), "git", "cmd", $"git{IOUtil.ExeExtension}"); ArgUtil.File(gitPath, nameof(gitPath)); executionContext.Output(StringUtil.Loc("Prepending0WithDirectoryContaining1", Constants.PathVariable, Path.GetFileName(gitPath))); var varUtil = HostContext.GetService <IVarUtil>(); varUtil.PrependPath(Path.GetDirectoryName(gitPath)); executionContext.Debug($"{Constants.PathVariable}: '{Environment.GetEnvironmentVariable(Constants.PathVariable)}'"); } else { // Validate git is in the PATH. var whichUtil = HostContext.GetService <IWhichUtil>(); whichUtil.Which("git", require: true); } // Override build.sourcesDirectory. // // Technically the value will be out of sync with the tracking file. The tracking file // is created during job initialization (Get Sources is later). That is OK, since the // local-run-sources-directory should not participate in cleanup anyway. string localDirectory = endpoint.Data?["localDirectory"]; ArgUtil.Directory(localDirectory, nameof(localDirectory)); ArgUtil.Directory(Path.Combine(localDirectory, ".git"), "localDotGitDirectory"); executionContext.Variables.Set(Constants.Variables.System.DefaultWorkingDirectory, localDirectory); executionContext.Variables.Set(Constants.Variables.Build.SourcesDirectory, localDirectory); executionContext.Variables.Set(Constants.Variables.Build.RepoLocalPath, localDirectory); // todo: consider support for clean return(Task.CompletedTask); }
public async Task RunPluginTaskAsync(IExecutionContext context, string plugin, Dictionary <string, string> inputs, Dictionary <string, string> environment, Variables runtimeVariables, EventHandler <ProcessDataReceivedEventArgs> outputHandler) { ArgUtil.NotNullOrEmpty(plugin, nameof(plugin)); // Only allow plugins we defined if (!_taskPlugins.Contains(plugin)) { throw new NotSupportedException(plugin); } // Resolve the working directory. string workingDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); ArgUtil.Directory(workingDirectory, nameof(workingDirectory)); // Agent.PluginHost string file = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), $"Agent.PluginHost{Util.IOUtil.ExeExtension}"); ArgUtil.File(file, $"Agent.PluginHost{Util.IOUtil.ExeExtension}"); var pluginContext = GeneratePluginExecutionContext(context, inputs, runtimeVariables); using (var processInvoker = HostContext.CreateService <IProcessInvoker>()) using (var redirectStandardIn = new InputQueue <string>()) { redirectStandardIn.Enqueue(JsonUtility.ToString(pluginContext)); processInvoker.OutputDataReceived += outputHandler; processInvoker.ErrorDataReceived += outputHandler; // Execute the process. Exit code 0 should always be returned. // A non-zero exit code indicates infrastructural failure. // Task failure should be communicated over STDOUT using ## commands. // Agent.PluginHost's arguments string arguments = $"task \"{plugin}\""; await processInvoker.ExecuteAsync(workingDirectory : workingDirectory, fileName : file, arguments : arguments, environment : environment, requireExitCodeZero : true, outputEncoding : Encoding.UTF8, killProcessOnCancel : false, redirectStandardIn : redirectStandardIn, cancellationToken : context.CancellationToken); } }
public async Task RunAsync(RunnerActionPluginExecutionContext executionContext, CancellationToken token) { string tempDirectory = executionContext.GetRunnerContext("temp"); ArgUtil.Directory(tempDirectory, nameof(tempDirectory)); // register problem matcher string matcherFile = Path.Combine(tempDirectory, $"git_{Guid.NewGuid()}.json"); File.WriteAllText(matcherFile, GitHubSourceProvider.ProblemMatcher, new UTF8Encoding(false)); executionContext.Output($"##[add-matcher]{matcherFile}"); try { await new GitHubSourceProvider().CleanupAsync(executionContext); } finally { executionContext.Output("##[remove-matcher owner=checkout-git]"); } }
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously (method has async logic on only certain platforms) public async Task RunAsync(ActionRunStage stage) #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { // Validate args. Trace.Entering(); ArgUtil.NotNull(Data, nameof(Data)); ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext)); #if OS_WINDOWS || OS_OSX throw new NotSupportedException($"Container action is only supported on Linux"); #else // Update the env dictionary. AddInputsToEnvironment(); var dockerManger = HostContext.GetService <IDockerCommandManager>(); // container image haven't built/pull if (Data.Image.StartsWith("docker://", StringComparison.OrdinalIgnoreCase)) { Data.Image = Data.Image.Substring("docker://".Length); } else if (Data.Image.EndsWith("Dockerfile") || Data.Image.EndsWith("dockerfile")) { // ensure docker file exist var dockerFile = Path.Combine(ActionDirectory, Data.Image); ArgUtil.File(dockerFile, nameof(Data.Image)); ExecutionContext.Output($"Dockerfile for action: '{dockerFile}'."); var imageName = $"{dockerManger.DockerInstanceLabel}:{ExecutionContext.Id.ToString("N")}"; var buildExitCode = await dockerManger.DockerBuild(ExecutionContext, ExecutionContext.GetGitHubContext("workspace"), Directory.GetParent(dockerFile).FullName, imageName); if (buildExitCode != 0) { throw new InvalidOperationException($"Docker build failed with exit code {buildExitCode}"); } Data.Image = imageName; } // run container var container = new ContainerInfo() { ContainerImage = Data.Image, ContainerName = ExecutionContext.Id.ToString("N"), ContainerDisplayName = $"{Pipelines.Validation.NameValidation.Sanitize(Data.Image)}_{Guid.NewGuid().ToString("N").Substring(0, 6)}", }; if (stage == ActionRunStage.Main) { if (!string.IsNullOrEmpty(Data.EntryPoint)) { // use entrypoint from action.yml container.ContainerEntryPoint = Data.EntryPoint; } else { // use entrypoint input, this is for action v1 which doesn't have action.yml container.ContainerEntryPoint = Inputs.GetValueOrDefault("entryPoint"); } } else if (stage == ActionRunStage.Post) { container.ContainerEntryPoint = Data.Cleanup; } // create inputs context for template evaluation var inputsContext = new DictionaryContextData(); if (this.Inputs != null) { foreach (var input in Inputs) { inputsContext.Add(input.Key, new StringContextData(input.Value)); } } var evaluateContext = new Dictionary <string, PipelineContextData>(StringComparer.OrdinalIgnoreCase); evaluateContext["inputs"] = inputsContext; var manifestManager = HostContext.GetService <IActionManifestManager>(); if (Data.Arguments != null) { container.ContainerEntryPointArgs = ""; var evaluatedArgs = manifestManager.EvaluateContainerArguments(ExecutionContext, Data.Arguments, evaluateContext); foreach (var arg in evaluatedArgs) { if (!string.IsNullOrEmpty(arg)) { container.ContainerEntryPointArgs = container.ContainerEntryPointArgs + $" \"{arg.Replace("\"", "\\\"")}\""; } else { container.ContainerEntryPointArgs = container.ContainerEntryPointArgs + " \"\""; } } } else { container.ContainerEntryPointArgs = Inputs.GetValueOrDefault("args"); } if (Data.Environment != null) { var evaluatedEnv = manifestManager.EvaluateContainerEnvironment(ExecutionContext, Data.Environment, evaluateContext); foreach (var env in evaluatedEnv) { if (!this.Environment.ContainsKey(env.Key)) { this.Environment[env.Key] = env.Value; } } } if (ExecutionContext.JobContext.Container.TryGetValue("network", out var networkContextData) && networkContextData is StringContextData networkStringData) { container.ContainerNetwork = networkStringData.ToString(); } var defaultWorkingDirectory = ExecutionContext.GetGitHubContext("workspace"); var tempDirectory = HostContext.GetDirectory(WellKnownDirectory.Temp); ArgUtil.NotNullOrEmpty(defaultWorkingDirectory, nameof(defaultWorkingDirectory)); ArgUtil.NotNullOrEmpty(tempDirectory, nameof(tempDirectory)); var tempHomeDirectory = Path.Combine(tempDirectory, "_github_home"); Directory.CreateDirectory(tempHomeDirectory); this.Environment["HOME"] = tempHomeDirectory; var tempWorkflowDirectory = Path.Combine(tempDirectory, "_github_workflow"); ArgUtil.Directory(tempWorkflowDirectory, nameof(tempWorkflowDirectory)); container.MountVolumes.Add(new MountVolume("/var/run/docker.sock", "/var/run/docker.sock")); container.MountVolumes.Add(new MountVolume(tempHomeDirectory, "/github/home")); container.MountVolumes.Add(new MountVolume(tempWorkflowDirectory, "/github/workflow")); container.MountVolumes.Add(new MountVolume(defaultWorkingDirectory, "/github/workspace")); container.AddPathTranslateMapping(tempHomeDirectory, "/github/home"); container.AddPathTranslateMapping(tempWorkflowDirectory, "/github/workflow"); container.AddPathTranslateMapping(defaultWorkingDirectory, "/github/workspace"); container.ContainerWorkDirectory = "/github/workspace"; // expose context to environment foreach (var context in ExecutionContext.ExpressionValues) { if (context.Value is IEnvironmentContextData runtimeContext && runtimeContext != null) { foreach (var env in runtimeContext.GetRuntimeEnvironmentVariables()) { Environment[env.Key] = env.Value; } } } // Add Actions Runtime server info var systemConnection = ExecutionContext.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase)); Environment["ACTIONS_RUNTIME_URL"] = systemConnection.Url.AbsoluteUri; Environment["ACTIONS_RUNTIME_TOKEN"] = systemConnection.Authorization.Parameters[EndpointAuthorizationParameters.AccessToken]; if (systemConnection.Data.TryGetValue("CacheServerUrl", out var cacheUrl) && !string.IsNullOrEmpty(cacheUrl)) { Environment["ACTIONS_CACHE_URL"] = cacheUrl; } foreach (var variable in this.Environment) { container.ContainerEnvironmentVariables[variable.Key] = container.TranslateToContainerPath(variable.Value); } using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager, container)) using (var stderrManager = new OutputManager(ExecutionContext, ActionCommandManager, container)) { var runExitCode = await dockerManger.DockerRun(ExecutionContext, container, stdoutManager.OnDataReceived, stderrManager.OnDataReceived); ExecutionContext.Debug($"Docker Action run completed with exit code {runExitCode}"); if (runExitCode != 0) { ExecutionContext.Result = TaskResult.Failed; } } #endif }
private async Task ProcessPluginCommandAsync(IAsyncCommandContext context, AgentCommandPluginExecutionContext pluginContext, string plugin, Command command, CancellationToken token) { // Resolve the working directory. string workingDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); ArgUtil.Directory(workingDirectory, nameof(workingDirectory)); // Agent.PluginHost string file = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), $"Agent.PluginHost{Util.IOUtil.ExeExtension}"); // Agent.PluginHost's arguments string arguments = $"command \"{plugin}\""; // Execute the process. Exit code 0 should always be returned. // A non-zero exit code indicates infrastructural failure. // Any content coming from STDERR will indicate the command process failed. // We can't use ## command for plugin to communicate, since we are already processing ## command using (var processInvoker = HostContext.CreateService <IProcessInvoker>()) { object stderrLock = new object(); List <string> stderr = new List <string>(); processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs e) => { context.Output(e.Data); }; processInvoker.ErrorDataReceived += (object sender, ProcessDataReceivedEventArgs e) => { lock (stderrLock) { stderr.Add(e.Data); }; }; var redirectStandardIn = new InputQueue <string>(); redirectStandardIn.Enqueue(JsonUtility.ToString(pluginContext)); int returnCode = await processInvoker.ExecuteAsync(workingDirectory : workingDirectory, fileName : file, arguments : arguments, environment : null, requireExitCodeZero : false, outputEncoding : null, killProcessOnCancel : false, redirectStandardIn : redirectStandardIn, cancellationToken : token); if (returnCode != 0) { context.Output(string.Join(Environment.NewLine, stderr)); throw new ProcessExitCodeException(returnCode, file, arguments); } else if (stderr.Count > 0) { throw new InvalidOperationException(string.Join(Environment.NewLine, stderr)); } else { // Everything works fine. // Return code is 0. // No STDERR comes out. } } }
public async Task RunAsync(RunnerActionPluginExecutionContext executionContext, CancellationToken token) { string runnerWorkspace = executionContext.GetRunnerContext("workspace"); ArgUtil.Directory(runnerWorkspace, nameof(runnerWorkspace)); string tempDirectory = executionContext.GetRunnerContext("temp"); ArgUtil.Directory(tempDirectory, nameof(tempDirectory)); var repoFullName = executionContext.GetInput(Pipelines.PipelineConstants.CheckoutTaskInputs.Repository); if (string.IsNullOrEmpty(repoFullName)) { repoFullName = executionContext.GetGitHubContext("repository"); } var repoFullNameSplit = repoFullName.Split("/", StringSplitOptions.RemoveEmptyEntries); if (repoFullNameSplit.Length != 2) { throw new ArgumentOutOfRangeException(repoFullName); } string expectRepoPath; var path = executionContext.GetInput(Pipelines.PipelineConstants.CheckoutTaskInputs.Path); if (!string.IsNullOrEmpty(path)) { expectRepoPath = IOUtil.ResolvePath(runnerWorkspace, path); if (!expectRepoPath.StartsWith(runnerWorkspace.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar)) { throw new ArgumentException($"Input path '{path}' should resolve to a directory under '{runnerWorkspace}', current resolved path '{expectRepoPath}'."); } } else { // When repository doesn't has path set, default to sources directory 1/repoName expectRepoPath = Path.Combine(runnerWorkspace, repoFullNameSplit[1]); } var workspaceRepo = executionContext.GetGitHubContext("repository"); // for self repository, we need to let the worker knows where it is after checkout. if (string.Equals(workspaceRepo, repoFullName, StringComparison.OrdinalIgnoreCase)) { var workspaceRepoPath = executionContext.GetGitHubContext("workspace"); executionContext.Debug($"Repository requires to be placed at '{expectRepoPath}', current location is '{workspaceRepoPath}'"); if (!string.Equals(workspaceRepoPath.Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), expectRepoPath.Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), IOUtil.FilePathStringComparison)) { executionContext.Output($"Repository is current at '{workspaceRepoPath}', move to '{expectRepoPath}'."); var count = 1; var staging = Path.Combine(tempDirectory, $"_{count}"); while (Directory.Exists(staging)) { count++; staging = Path.Combine(tempDirectory, $"_{count}"); } try { executionContext.Debug($"Move existing repository '{workspaceRepoPath}' to '{expectRepoPath}' via staging directory '{staging}'."); IOUtil.MoveDirectory(workspaceRepoPath, expectRepoPath, staging, CancellationToken.None); } catch (Exception ex) { executionContext.Debug("Catch exception during repository move."); executionContext.Debug(ex.ToString()); executionContext.Warning("Unable move and reuse existing repository to required location."); IOUtil.DeleteDirectory(expectRepoPath, CancellationToken.None); } executionContext.Output($"Repository will locate at '{expectRepoPath}'."); } executionContext.Debug($"Update workspace repository location."); executionContext.SetRepositoryPath(repoFullName, expectRepoPath, true); } string sourceBranch; string sourceVersion; string refInput = executionContext.GetInput(Pipelines.PipelineConstants.CheckoutTaskInputs.Ref); if (string.IsNullOrEmpty(refInput)) { sourceBranch = executionContext.GetGitHubContext("ref"); sourceVersion = executionContext.GetGitHubContext("sha"); } else { sourceBranch = refInput; sourceVersion = executionContext.GetInput(Pipelines.PipelineConstants.CheckoutTaskInputs.Version); // version get removed when checkout move to repo in the graph if (string.IsNullOrEmpty(sourceVersion) && RegexUtility.IsMatch(sourceBranch, WellKnownRegularExpressions.SHA1)) { sourceVersion = sourceBranch; // If Ref is a SHA and the repo is self, we need to use github.ref as source branch since it might be refs/pull/* if (string.Equals(workspaceRepo, repoFullName, StringComparison.OrdinalIgnoreCase)) { sourceBranch = executionContext.GetGitHubContext("ref"); } else { sourceBranch = "refs/heads/master"; } } } bool clean = StringUtil.ConvertToBoolean(executionContext.GetInput(Pipelines.PipelineConstants.CheckoutTaskInputs.Clean), true); string submoduleInput = executionContext.GetInput(Pipelines.PipelineConstants.CheckoutTaskInputs.Submodules); int fetchDepth = 0; if (!int.TryParse(executionContext.GetInput("fetch-depth"), out fetchDepth) || fetchDepth < 0) { fetchDepth = 0; } bool gitLfsSupport = StringUtil.ConvertToBoolean(executionContext.GetInput(Pipelines.PipelineConstants.CheckoutTaskInputs.Lfs)); string accessToken = executionContext.GetInput(Pipelines.PipelineConstants.CheckoutTaskInputs.Token); if (string.IsNullOrEmpty(accessToken)) { accessToken = executionContext.GetGitHubContext("token"); } // register problem matcher string problemMatcher = @" { ""problemMatcher"": [ { ""owner"": ""checkout-git"", ""pattern"": [ { ""regexp"": ""^fatal: (.*)$"", ""message"": 1 } ] } ] }"; string matcherFile = Path.Combine(tempDirectory, $"git_{Guid.NewGuid()}.json"); File.WriteAllText(matcherFile, problemMatcher, new UTF8Encoding(false)); executionContext.Output($"##[add-matcher]{matcherFile}"); try { await new GitHubSourceProvider().GetSourceAsync(executionContext, expectRepoPath, repoFullName, sourceBranch, sourceVersion, clean, submoduleInput, fetchDepth, gitLfsSupport, accessToken, token); } finally { executionContext.Output("##[remove-matcher owner=checkout-git]"); } }
public void UploadDiagnosticLogs(IExecutionContext executionContext, IExecutionContext parentContext, Pipelines.AgentJobRequestMessage message, DateTime jobStartTimeUtc) { executionContext.Debug("Starting diagnostic file upload."); // Setup folders // \_layout\_work\_temp\[jobname-support] executionContext.Debug("Setting up diagnostic log folders."); string tempDirectory = HostContext.GetDirectory(WellKnownDirectory.Temp); ArgUtil.Directory(tempDirectory, nameof(tempDirectory)); string supportRootFolder = Path.Combine(tempDirectory, message.JobName + "-support"); Directory.CreateDirectory(supportRootFolder); // \_layout\_work\_temp\[jobname-support]\files executionContext.Debug("Creating diagnostic log files folder."); string supportFilesFolder = Path.Combine(supportRootFolder, "files"); Directory.CreateDirectory(supportFilesFolder); // Create the environment file // \_layout\_work\_temp\[jobname-support]\files\environment.txt var configurationStore = HostContext.GetService <IConfigurationStore>(); RunnerSettings settings = configurationStore.GetSettings(); int runnerId = settings.AgentId; string runnerName = settings.AgentName; int poolId = settings.PoolId; // Copy worker diagnostic log files List <string> workerDiagnosticLogFiles = GetWorkerDiagnosticLogFiles(HostContext.GetDirectory(WellKnownDirectory.Diag), jobStartTimeUtc); executionContext.Debug($"Copying {workerDiagnosticLogFiles.Count()} worker diagnostic logs."); foreach (string workerLogFile in workerDiagnosticLogFiles) { ArgUtil.File(workerLogFile, nameof(workerLogFile)); string destination = Path.Combine(supportFilesFolder, Path.GetFileName(workerLogFile)); File.Copy(workerLogFile, destination); } // Copy runner diag log files List <string> runnerDiagnosticLogFiles = GetRunnerDiagnosticLogFiles(HostContext.GetDirectory(WellKnownDirectory.Diag), jobStartTimeUtc); executionContext.Debug($"Copying {runnerDiagnosticLogFiles.Count()} runner diagnostic logs."); foreach (string runnerLogFile in runnerDiagnosticLogFiles) { ArgUtil.File(runnerLogFile, nameof(runnerLogFile)); string destination = Path.Combine(supportFilesFolder, Path.GetFileName(runnerLogFile)); File.Copy(runnerLogFile, destination); } executionContext.Debug("Zipping diagnostic files."); string buildNumber = executionContext.Variables.Build_Number ?? "UnknownBuildNumber"; string buildName = $"Build {buildNumber}"; string phaseName = executionContext.Variables.System_PhaseDisplayName ?? "UnknownPhaseName"; // zip the files string diagnosticsZipFileName = $"{buildName}-{phaseName}.zip"; string diagnosticsZipFilePath = Path.Combine(supportRootFolder, diagnosticsZipFileName); ZipFile.CreateFromDirectory(supportFilesFolder, diagnosticsZipFilePath); // upload the json metadata file executionContext.Debug("Uploading diagnostic metadata file."); string metadataFileName = $"diagnostics-{buildName}-{phaseName}.json"; string metadataFilePath = Path.Combine(supportFilesFolder, metadataFileName); string phaseResult = GetTaskResultAsString(executionContext.Result); IOUtil.SaveObject(new DiagnosticLogMetadata(runnerName, runnerId, poolId, phaseName, diagnosticsZipFileName, phaseResult), metadataFilePath); // TODO: Remove the parentContext Parameter and replace this with executioncontext. Currently a bug exists where these files do not upload correctly using that context. parentContext.QueueAttachFile(type: CoreAttachmentType.DiagnosticLog, name: metadataFileName, filePath: metadataFilePath); parentContext.QueueAttachFile(type: CoreAttachmentType.DiagnosticLog, name: diagnosticsZipFileName, filePath: diagnosticsZipFilePath); executionContext.Debug("Diagnostic file upload complete."); }
public async Task UploadDiagnosticLogsAsync(IExecutionContext executionContext, Pipelines.AgentJobRequestMessage message, DateTime jobStartTimeUtc) { executionContext.Debug("Starting diagnostic file upload."); // Setup folders // \_layout\_work\_temp\[jobname-support] executionContext.Debug("Setting up diagnostic log folders."); string tempDirectory = HostContext.GetDirectory(WellKnownDirectory.Temp); ArgUtil.Directory(tempDirectory, nameof(tempDirectory)); string supportRootFolder = Path.Combine(tempDirectory, message.JobName + "-support"); Directory.CreateDirectory(supportRootFolder); // \_layout\_work\_temp\[jobname-support]\files executionContext.Debug("Creating diagnostic log files folder."); string supportFilesFolder = Path.Combine(supportRootFolder, "files"); Directory.CreateDirectory(supportFilesFolder); // Create the environment file // \_layout\_work\_temp\[jobname-support]\files\environment.txt var configurationStore = HostContext.GetService <IConfigurationStore>(); AgentSettings settings = configurationStore.GetSettings(); int agentId = settings.AgentId; string agentName = settings.AgentName; int poolId = settings.PoolId; executionContext.Debug("Creating diagnostic log environment file."); string environmentFile = Path.Combine(supportFilesFolder, "environment.txt"); string content = await GetEnvironmentContent(agentId, agentName, message.Steps); File.WriteAllText(environmentFile, content); // Create the capabilities file var capabilitiesManager = HostContext.GetService <ICapabilitiesManager>(); Dictionary <string, string> capabilities = await capabilitiesManager.GetCapabilitiesAsync(configurationStore.GetSettings(), default(CancellationToken)); executionContext.Debug("Creating capabilities file."); string capabilitiesFile = Path.Combine(supportFilesFolder, "capabilities.txt"); string capabilitiesContent = GetCapabilitiesContent(capabilities); File.WriteAllText(capabilitiesFile, capabilitiesContent); // Copy worker diag log files List <string> workerDiagLogFiles = GetWorkerDiagLogFiles(HostContext.GetDirectory(WellKnownDirectory.Diag), jobStartTimeUtc); executionContext.Debug($"Copying {workerDiagLogFiles.Count()} worker diag logs."); foreach (string workerLogFile in workerDiagLogFiles) { ArgUtil.File(workerLogFile, nameof(workerLogFile)); string destination = Path.Combine(supportFilesFolder, Path.GetFileName(workerLogFile)); File.Copy(workerLogFile, destination); } // Copy agent diag log files List <string> agentDiagLogFiles = GetAgentDiagLogFiles(HostContext.GetDirectory(WellKnownDirectory.Diag), jobStartTimeUtc); executionContext.Debug($"Copying {agentDiagLogFiles.Count()} agent diag logs."); foreach (string agentLogFile in agentDiagLogFiles) { ArgUtil.File(agentLogFile, nameof(agentLogFile)); string destination = Path.Combine(supportFilesFolder, Path.GetFileName(agentLogFile)); File.Copy(agentLogFile, destination); } executionContext.Debug("Zipping diagnostic files."); string buildNumber = executionContext.Variables.Build_Number ?? "UnknownBuildNumber"; string buildName = $"Build {buildNumber}"; string phaseName = executionContext.Variables.System_PhaseDisplayName ?? "UnknownPhaseName"; // zip the files string diagnosticsZipFileName = $"{buildName}-{phaseName}.zip"; string diagnosticsZipFilePath = Path.Combine(supportRootFolder, diagnosticsZipFileName); ZipFile.CreateFromDirectory(supportFilesFolder, diagnosticsZipFilePath); // upload the json metadata file executionContext.Debug("Uploading diagnostic metadata file."); string metadataFileName = $"diagnostics-{buildName}-{phaseName}.json"; string metadataFilePath = Path.Combine(supportFilesFolder, metadataFileName); string phaseResult = GetTaskResultAsString(executionContext.Result); IOUtil.SaveObject(new DiagnosticLogMetadata(agentName, agentId, poolId, phaseName, diagnosticsZipFileName, phaseResult), metadataFilePath); executionContext.QueueAttachFile(type: CoreAttachmentType.DiagnosticLog, name: metadataFileName, filePath: metadataFilePath); executionContext.QueueAttachFile(type: CoreAttachmentType.DiagnosticLog, name: diagnosticsZipFileName, filePath: diagnosticsZipFilePath); executionContext.Debug("Diagnostic file upload complete."); }
public async Task UploadDiagnosticLogsAsync(IExecutionContext executionContext, Pipelines.AgentJobRequestMessage message, DateTime jobStartTimeUtc) { ArgUtil.NotNull(executionContext, nameof(executionContext)); ArgUtil.NotNull(message, nameof(message)); executionContext.Debug("Starting diagnostic file upload."); // Setup folders // \_layout\_work\_temp\[jobname-support] executionContext.Debug("Setting up diagnostic log folders."); string tempDirectory = HostContext.GetDirectory(WellKnownDirectory.Temp); ArgUtil.Directory(tempDirectory, nameof(tempDirectory)); string supportRootFolder = Path.Combine(tempDirectory, message.JobName + "-support"); Directory.CreateDirectory(supportRootFolder); // \_layout\_work\_temp\[jobname-support]\files executionContext.Debug("Creating diagnostic log files folder."); string supportFilesFolder = Path.Combine(supportRootFolder, "files"); Directory.CreateDirectory(supportFilesFolder); // Create the environment file // \_layout\_work\_temp\[jobname-support]\files\environment.txt var configurationStore = HostContext.GetService <IConfigurationStore>(); AgentSettings settings = configurationStore.GetSettings(); int agentId = settings.AgentId; string agentName = settings.AgentName; int poolId = settings.PoolId; executionContext.Debug("Creating diagnostic log environment file."); string environmentFile = Path.Combine(supportFilesFolder, "environment.txt"); string content = await GetEnvironmentContent(agentId, agentName, message.Steps); File.WriteAllText(environmentFile, content); // Create the capabilities file var capabilitiesManager = HostContext.GetService <ICapabilitiesManager>(); Dictionary <string, string> capabilities = await capabilitiesManager.GetCapabilitiesAsync(configurationStore.GetSettings(), default(CancellationToken)); executionContext.Debug("Creating capabilities file."); string capabilitiesFile = Path.Combine(supportFilesFolder, "capabilities.txt"); string capabilitiesContent = GetCapabilitiesContent(capabilities); File.WriteAllText(capabilitiesFile, capabilitiesContent); // Copy worker diag log files List <string> workerDiagLogFiles = GetWorkerDiagLogFiles(HostContext.GetDirectory(WellKnownDirectory.Diag), jobStartTimeUtc); executionContext.Debug($"Copying {workerDiagLogFiles.Count()} worker diag logs."); foreach (string workerLogFile in workerDiagLogFiles) { ArgUtil.File(workerLogFile, nameof(workerLogFile)); string destination = Path.Combine(supportFilesFolder, Path.GetFileName(workerLogFile)); File.Copy(workerLogFile, destination); } // Copy agent diag log files List <string> agentDiagLogFiles = GetAgentDiagLogFiles(HostContext.GetDirectory(WellKnownDirectory.Diag), jobStartTimeUtc); executionContext.Debug($"Copying {agentDiagLogFiles.Count()} agent diag logs."); foreach (string agentLogFile in agentDiagLogFiles) { ArgUtil.File(agentLogFile, nameof(agentLogFile)); string destination = Path.Combine(supportFilesFolder, Path.GetFileName(agentLogFile)); File.Copy(agentLogFile, destination); } // Read and add to logs waagent.conf settings on Linux if (PlatformUtil.RunningOnLinux) { executionContext.Debug("Dumping of waagent.conf file"); string waagentDumpFile = Path.Combine(supportFilesFolder, "waagentConf.txt"); string configFileName = "waagent.conf"; try { string filePath = Directory.GetFiles("/etc", configFileName).FirstOrDefault(); if (!string.IsNullOrWhiteSpace(filePath)) { string waagentContent = File.ReadAllText(filePath); File.AppendAllText(waagentDumpFile, "waagent.conf settings"); File.AppendAllText(waagentDumpFile, Environment.NewLine); File.AppendAllText(waagentDumpFile, waagentContent); executionContext.Debug("Dumping waagent.conf file is completed."); } else { executionContext.Debug("waagent.conf file wasn't found. Dumping was not done."); } } catch (Exception ex) { string warningMessage = $"Dumping of waagent.conf was not completed successfully. Error message: {ex.Message}"; executionContext.Warning(warningMessage); } } // Copy cloud-init log files from linux machines if (PlatformUtil.RunningOnLinux) { executionContext.Debug("Dumping cloud-init logs."); string logsFilePath = $"{HostContext.GetDirectory(WellKnownDirectory.Diag)}/cloudinit-{jobStartTimeUtc.ToString("yyyyMMdd-HHmmss")}-logs.tar.gz"; string resultLogs = await DumpCloudInitLogs(logsFilePath); executionContext.Debug(resultLogs); if (File.Exists(logsFilePath)) { string destination = Path.Combine(supportFilesFolder, Path.GetFileName(logsFilePath)); File.Copy(logsFilePath, destination); executionContext.Debug("Cloud-init logs added to the diagnostics archive."); } else { executionContext.Debug("Cloud-init logs were not found."); } executionContext.Debug("Dumping cloud-init logs is ended."); } // Copy event logs for windows machines if (PlatformUtil.RunningOnWindows) { executionContext.Debug("Dumping event viewer logs for current job."); try { string eventLogsFile = $"{HostContext.GetDirectory(WellKnownDirectory.Diag)}/EventViewer-{ jobStartTimeUtc.ToString("yyyyMMdd-HHmmss") }.log"; await DumpCurrentJobEventLogs(executionContext, eventLogsFile, jobStartTimeUtc); string destination = Path.Combine(supportFilesFolder, Path.GetFileName(eventLogsFile)); File.Copy(eventLogsFile, destination); } catch (Exception ex) { executionContext.Debug("Failed to dump event viewer logs. Skipping."); executionContext.Debug($"Error message: {ex}"); } } if (PlatformUtil.RunningOnLinux && !PlatformUtil.RunningOnRHEL6) { executionContext.Debug("Dumping info about invalid MD5 sums of installed packages."); try { string packageVerificationResults = await GetPackageVerificationResult(); IEnumerable <string> brokenPackagesInfo = packageVerificationResults .Split("\n") .Where((line) => !String.IsNullOrEmpty(line) && !line.EndsWith("OK")); string brokenPackagesLogsPath = $"{HostContext.GetDirectory(WellKnownDirectory.Diag)}/BrokenPackages-{ jobStartTimeUtc.ToString("yyyyMMdd-HHmmss") }.log"; File.AppendAllLines(brokenPackagesLogsPath, brokenPackagesInfo); string destination = Path.Combine(supportFilesFolder, Path.GetFileName(brokenPackagesLogsPath)); File.Copy(brokenPackagesLogsPath, destination); } catch (Exception ex) { executionContext.Debug("Failed to dump broken packages logs. Skipping."); executionContext.Debug($"Error message: {ex}"); } } else { executionContext.Debug("The platform is not based on Debian - skipping debsums check."); } try { executionContext.Debug("Starting dumping Agent Azure VM extension logs."); bool logsSuccessfullyDumped = DumpAgentExtensionLogs(executionContext, supportFilesFolder, jobStartTimeUtc); if (logsSuccessfullyDumped) { executionContext.Debug("Agent Azure VM extension logs successfully dumped."); } else { executionContext.Debug("Agent Azure VM extension logs not found. Skipping."); } } catch (Exception ex) { executionContext.Debug("Failed to dump Agent Azure VM extension logs. Skipping."); executionContext.Debug($"Error message: {ex}"); } executionContext.Debug("Zipping diagnostic files."); string buildNumber = executionContext.Variables.Build_Number ?? "UnknownBuildNumber"; string buildName = $"Build {buildNumber}"; string phaseName = executionContext.Variables.System_PhaseDisplayName ?? "UnknownPhaseName"; // zip the files string diagnosticsZipFileName = $"{buildName}-{phaseName}.zip"; string diagnosticsZipFilePath = Path.Combine(supportRootFolder, diagnosticsZipFileName); ZipFile.CreateFromDirectory(supportFilesFolder, diagnosticsZipFilePath); // upload the json metadata file executionContext.Debug("Uploading diagnostic metadata file."); string metadataFileName = $"diagnostics-{buildName}-{phaseName}.json"; string metadataFilePath = Path.Combine(supportFilesFolder, metadataFileName); string phaseResult = GetTaskResultAsString(executionContext.Result); IOUtil.SaveObject(new DiagnosticLogMetadata(agentName, agentId, poolId, phaseName, diagnosticsZipFileName, phaseResult), metadataFilePath); executionContext.QueueAttachFile(type: CoreAttachmentType.DiagnosticLog, name: metadataFileName, filePath: metadataFilePath); executionContext.QueueAttachFile(type: CoreAttachmentType.DiagnosticLog, name: diagnosticsZipFileName, filePath: diagnosticsZipFilePath); executionContext.Debug("Diagnostic file upload complete."); }
public async Task RunAsync(ActionRunStage stage) { // Validate args. Trace.Entering(); ArgUtil.NotNull(Data, nameof(Data)); ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext)); ArgUtil.NotNull(Inputs, nameof(Inputs)); ArgUtil.Directory(ActionDirectory, nameof(ActionDirectory)); // Update the env dictionary. AddInputsToEnvironment(); AddPrependPathToEnvironment(); // expose context to environment foreach (var context in ExecutionContext.ExpressionValues) { if (context.Value is IEnvironmentContextData runtimeContext && runtimeContext != null) { foreach (var env in runtimeContext.GetRuntimeEnvironmentVariables()) { Environment[env.Key] = env.Value; } } } // Add Actions Runtime server info var systemConnection = ExecutionContext.Global.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase)); Environment["ACTIONS_RUNTIME_URL"] = systemConnection.Url.AbsoluteUri; Environment["ACTIONS_RUNTIME_TOKEN"] = systemConnection.Authorization.Parameters[EndpointAuthorizationParameters.AccessToken]; if (systemConnection.Data.TryGetValue("CacheServerUrl", out var cacheUrl) && !string.IsNullOrEmpty(cacheUrl)) { Environment["ACTIONS_CACHE_URL"] = cacheUrl; } // Resolve the target script. string target = null; if (stage == ActionRunStage.Main) { target = Data.Script; } else if (stage == ActionRunStage.Pre) { target = Data.Pre; } else if (stage == ActionRunStage.Post) { target = Data.Post; } ArgUtil.NotNullOrEmpty(target, nameof(target)); target = Path.Combine(ActionDirectory, target); ArgUtil.File(target, nameof(target)); // Resolve the working directory. string workingDirectory = ExecutionContext.GetGitHubContext("workspace"); if (string.IsNullOrEmpty(workingDirectory)) { workingDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); } var nodeRuntimeVersion = await StepHost.DetermineNodeRuntimeVersion(ExecutionContext); string file = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), nodeRuntimeVersion, "bin", $"node{IOUtil.ExeExtension}"); if (System.Environment.GetEnvironmentVariable("K8S_POD_NAME") != null) { file = Path.Combine(Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), "__externals_copy"), nodeRuntimeVersion, "bin", $"node{IOUtil.ExeExtension}"); } // Format the arguments passed to node. // 1) Wrap the script file path in double quotes. // 2) Escape double quotes within the script file path. Double-quote is a valid // file name character on Linux. string arguments = StepHost.ResolvePathForStepHost(StringUtil.Format(@"""{0}""", target.Replace(@"""", @"\"""))); #if OS_WINDOWS // It appears that node.exe outputs UTF8 when not in TTY mode. Encoding outputEncoding = Encoding.UTF8; #else // Let .NET choose the default. Encoding outputEncoding = null; #endif using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager)) using (var stderrManager = new OutputManager(ExecutionContext, ActionCommandManager)) { StepHost.OutputDataReceived += stdoutManager.OnDataReceived; StepHost.ErrorDataReceived += stderrManager.OnDataReceived; // Execute the process. Exit code 0 should always be returned. // A non-zero exit code indicates infrastructural failure. // Task failure should be communicated over STDOUT using ## commands. Task <int> step = StepHost.ExecuteAsync(workingDirectory: StepHost.ResolvePathForStepHost(workingDirectory), fileName: StepHost.ResolvePathForStepHost(file), arguments: arguments, environment: Environment, requireExitCodeZero: false, outputEncoding: outputEncoding, killProcessOnCancel: false, inheritConsoleHandler: !ExecutionContext.Global.Variables.Retain_Default_Encoding, cancellationToken: ExecutionContext.CancellationToken); // Wait for either the node exit or force finish through ##vso command await System.Threading.Tasks.Task.WhenAny(step, ExecutionContext.ForceCompleted); if (ExecutionContext.ForceCompleted.IsCompleted) { ExecutionContext.Debug("The task was marked as \"done\", but the process has not closed after 5 seconds. Treating the task as complete."); } else { var exitCode = await step; ExecutionContext.Debug($"Node Action run completed with exit code {exitCode}"); if (exitCode != 0) { ExecutionContext.Result = TaskResult.Failed; } } } }
public async Task RunAsync() { // Validate args. Trace.Entering(); ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext)); ArgUtil.NotNull(Inputs, nameof(Inputs)); ArgUtil.Directory(TaskDirectory, nameof(TaskDirectory)); // Resolve the target script. string target = GetTarget(); ArgUtil.NotNullOrEmpty(target, nameof(target)); string scriptFile = Path.Combine(TaskDirectory, target); ArgUtil.File(scriptFile, nameof(scriptFile)); // Determine the working directory. string workingDirectory = GetWorkingDirectory(); if (String.IsNullOrEmpty(workingDirectory)) { workingDirectory = Path.GetDirectoryName(scriptFile); } else { if (!Directory.Exists(workingDirectory)) { Directory.CreateDirectory(workingDirectory); } } // Copy the OM binaries into the legacy host folder. ExecutionContext.Output(StringUtil.Loc("PrepareTaskExecutionHandler")); IOUtil.CopyDirectory( source: HostContext.GetDirectory(WellKnownDirectory.ServerOM), target: HostContext.GetDirectory(WellKnownDirectory.LegacyPSHost), cancellationToken: ExecutionContext.CancellationToken); Trace.Info("Finished copying files."); // Add the legacy ps host environment variables. AddLegacyHostEnvironmentVariables(scriptFile: scriptFile, workingDirectory: workingDirectory); AddPrependPathToEnvironment(); // Add proxy setting to LegacyVSTSPowerShellHost.exe.config var agentProxy = HostContext.GetService <IVstsAgentWebProxy>(); if (!string.IsNullOrEmpty(agentProxy.ProxyAddress)) { AddProxySetting(agentProxy); } // Invoke the process. using (var processInvoker = HostContext.CreateService <IProcessInvoker>()) { processInvoker.OutputDataReceived += OnDataReceived; processInvoker.ErrorDataReceived += OnDataReceived; try { String vstsPSHostExe = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.LegacyPSHost), "LegacyVSTSPowerShellHost.exe"); Int32 exitCode = await processInvoker.ExecuteAsync(workingDirectory : workingDirectory, fileName : vstsPSHostExe, arguments : "", environment : Environment, requireExitCodeZero : false, outputEncoding : null, killProcessOnCancel : false, redirectStandardIn : null, inheritConsoleHandler : !ExecutionContext.Variables.Retain_Default_Encoding, cancellationToken : ExecutionContext.CancellationToken); // the exit code from vstsPSHost.exe indicate how many error record we get during execution // -1 exit code means infrastructure failure of Host itself. // this is to match current handler's logic. if (exitCode > 0) { if (ExecutionContext.Result != null) { ExecutionContext.Debug($"Task result already set. Not failing due to error count ({exitCode})."); } else { // We fail task and add issue. ExecutionContext.Result = TaskResult.Failed; ExecutionContext.Error(StringUtil.Loc("PSScriptError", exitCode)); } } else if (exitCode < 0) { // We fail task and add issue. ExecutionContext.Result = TaskResult.Failed; ExecutionContext.Error(StringUtil.Loc("VSTSHostNonZeroReturn", exitCode)); } } finally { processInvoker.OutputDataReceived -= OnDataReceived; processInvoker.ErrorDataReceived -= OnDataReceived; } } }
public Task StartAsync(IExecutionContext context, List <IStep> steps, CancellationToken token) { Trace.Entering(); ArgUtil.NotNull(context, nameof(context)); List <PluginInfo> enabledPlugins = new List <PluginInfo>(); if (context.Variables.GetBoolean("agent.disablelogplugin") ?? false) { // all log plugs are disabled context.Debug("All log plugins are disabled."); } else { foreach (var plugin in _logPlugins) { if (context.Variables.GetBoolean($"agent.disablelogplugin.{plugin.Key}") ?? false) { // skip plugin context.Debug($"Log plugin '{plugin.Key}' is disabled."); continue; } else { enabledPlugins.Add(plugin.Value); } } } if (enabledPlugins.Count > 0) { // Resolve the working directory. string workingDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); ArgUtil.Directory(workingDirectory, nameof(workingDirectory)); // Agent.PluginHost string file = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), $"Agent.PluginHost{Util.IOUtil.ExeExtension}"); ArgUtil.File(file, $"Agent.PluginHost{Util.IOUtil.ExeExtension}"); // Agent.PluginHost's arguments string arguments = $"log \"{_instanceId.ToString("D")}\""; var processInvoker = HostContext.CreateService <IProcessInvoker>(); processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs e) => { if (e.Data != null) { _outputs.Enqueue(e.Data); } }; processInvoker.ErrorDataReceived += (object sender, ProcessDataReceivedEventArgs e) => { if (e.Data != null) { _outputs.Enqueue(e.Data); } }; _pluginHostProcess?.Dispose(); _pluginHostProcess = processInvoker.ExecuteAsync(workingDirectory: workingDirectory, fileName: file, arguments: arguments, environment: null, requireExitCodeZero: true, outputEncoding: Encoding.UTF8, killProcessOnCancel: true, redirectStandardIn: _redirectedStdin, inheritConsoleHandler: false, keepStandardInOpen: true, cancellationToken: token); // construct plugin context AgentLogPluginHostContext pluginContext = new AgentLogPluginHostContext { PluginAssemblies = new List <string>(), Repositories = context.Repositories, Endpoints = context.Endpoints, Variables = new Dictionary <string, VariableValue>(), Steps = new Dictionary <string, Pipelines.TaskStepDefinitionReference>() }; // plugins pluginContext.PluginAssemblies.AddRange(_logPlugins.Values.Select(x => x.AssemblyName)); var target = context.StepTarget(); Variables.TranslationMethod translateToHostPath = Variables.DefaultStringTranslator; ContainerInfo containerInfo = target as ContainerInfo; // Since plugins run on the host, but the inputs and variables have already been translated // to the container path, we need to convert them back to the host path // TODO: look to see if there is a better way to not have translate these back if (containerInfo != null) { translateToHostPath = (string val) => { return(containerInfo.TranslateToHostPath(val)); }; } // variables context.Variables.CopyInto(pluginContext.Variables, translateToHostPath); // steps foreach (var step in steps) { var taskStep = step as ITaskRunner; if (taskStep != null) { pluginContext.Steps[taskStep.ExecutionContext.Id.ToString("D")] = taskStep.Task.Reference; } } Trace.Info("Send serialized context through STDIN"); _redirectedStdin.Enqueue(JsonUtility.ToString(pluginContext)); foreach (var plugin in _logPlugins) { context.Output($"Plugin: '{plugin.Value.FriendlyName}' is running in background."); } } return(Task.CompletedTask); }
public async Task RunPluginTaskAsync(IExecutionContext context, string plugin, Dictionary <string, string> inputs, Dictionary <string, string> environment, Variables runtimeVariables, EventHandler <ProcessDataReceivedEventArgs> outputHandler) { ArgUtil.NotNullOrEmpty(plugin, nameof(plugin)); // Only allow plugins we defined if (!_taskPlugins.Contains(plugin)) { throw new NotSupportedException(plugin); } // Resolve the working directory. string workingDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); ArgUtil.Directory(workingDirectory, nameof(workingDirectory)); // Agent.PluginHost string file = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), $"Agent.PluginHost{Util.IOUtil.ExeExtension}"); ArgUtil.File(file, $"Agent.PluginHost{Util.IOUtil.ExeExtension}"); // Agent.PluginHost's arguments string arguments = $"task \"{plugin}\""; // construct plugin context AgentTaskPluginExecutionContext pluginContext = new AgentTaskPluginExecutionContext { Inputs = inputs, Repositories = context.Repositories, Endpoints = context.Endpoints }; // variables foreach (var publicVar in runtimeVariables.Public) { pluginContext.Variables[publicVar.Key] = publicVar.Value; } foreach (var publicVar in runtimeVariables.Private) { pluginContext.Variables[publicVar.Key] = new VariableValue(publicVar.Value, true); } // task variables (used by wrapper task) foreach (var publicVar in context.TaskVariables.Public) { pluginContext.TaskVariables[publicVar.Key] = publicVar.Value; } foreach (var publicVar in context.TaskVariables.Private) { pluginContext.TaskVariables[publicVar.Key] = new VariableValue(publicVar.Value, true); } using (var processInvoker = HostContext.CreateService <IProcessInvoker>()) { processInvoker.OutputDataReceived += outputHandler; processInvoker.ErrorDataReceived += outputHandler; // Execute the process. Exit code 0 should always be returned. // A non-zero exit code indicates infrastructural failure. // Task failure should be communicated over STDOUT using ## commands. await processInvoker.ExecuteAsync(workingDirectory : workingDirectory, fileName : file, arguments : arguments, environment : environment, requireExitCodeZero : true, outputEncoding : null, killProcessOnCancel : false, contentsToStandardIn : new List <string>() { JsonUtility.ToString(pluginContext) }, cancellationToken : context.CancellationToken); } }
public async Task RunAsync() { // Validate args. Trace.Entering(); ArgUtil.NotNull(Data, nameof(Data)); ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext)); ArgUtil.NotNull(Inputs, nameof(Inputs)); ArgUtil.Directory(TaskDirectory, nameof(TaskDirectory)); // Update the env dictionary. AddVariablesToEnvironment(excludeNames: true, excludeSecrets: true); // Determine whether to fail on STDERR. _failOnStandardError = StringUtil.ConvertToBoolean(Data.FailOnStandardError, true); // Default to true. // Get the script file. string scriptFile = null; try { if (string.Equals(Data.ScriptType, InlineScriptType, StringComparison.OrdinalIgnoreCase)) { // TODO: Write this file under the _work folder and clean it up at the beginning of the next build? // Write the inline script to a temp file. string tempDirectory = Path.GetTempPath(); ArgUtil.Directory(tempDirectory, nameof(tempDirectory)); scriptFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.ps1"); Trace.Info("Writing inline script to temp file: '{0}'", scriptFile); File.WriteAllText(scriptFile, Data.InlineScript ?? string.Empty, Encoding.UTF8); } else { // TODO: If not rooted, WHICH the file if it doesn't contain any slashes. // Assert the target file. ArgUtil.NotNullOrEmpty(Data.Target, nameof(Data.Target)); scriptFile = Data.Target; } // Define the nested expression to invoke the user-specified script file and arguments. // Use the dot operator (".") to run the script in the same scope. string nestedExpression = StringUtil.Format( ". '{0}' {1}", scriptFile.Trim('"').Replace("'", "''"), Data.ArgumentFormat); // Craft the args to pass to PowerShell.exe. The user-defined expression is jammed in // as an encrypted base 64 string to a wrapper command. This solves a couple problems: // 1) Avoids quoting issues by jamming all of the user input into a base-64 encoded. // 2) Handles setting the exit code. // // The goal here is to jam everything into a base 64 encoded string so that quoting // issues can be avoided. The data needs to be encrypted because base 64 encoding the // data circumvents the logger's secret-masking behavior. string entropy; string powerShellExeArgs = StringUtil.Format( "-NoLogo -Sta -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command \"try {{ $null = [System.Security.Cryptography.ProtectedData] }} catch {{ Write-Verbose 'Adding assemly: System.Security' ; Add-Type -AssemblyName 'System.Security' ; $null = [System.Security.Cryptography.ProtectedData] }} ; Invoke-Expression -Command ([System.Text.Encoding]::UTF8.GetString([System.Security.Cryptography.ProtectedData]::Unprotect([System.Convert]::FromBase64String('{0}'), [System.Convert]::FromBase64String('{1}'), [System.Security.Cryptography.DataProtectionScope]::CurrentUser))) ; if (!(Test-Path -LiteralPath variable:\\LastExitCode)) {{ Write-Verbose 'Last exit code is not set.' }} else {{ Write-Verbose ('$LastExitCode: {{0}}' -f $LastExitCode) ; exit $LastExitCode }}\"", Encrypt(nestedExpression, out entropy), entropy); // Resolve powershell.exe. string powerShellExe = HostContext.GetService <IPowerShellExeUtil>().GetPath(); ArgUtil.NotNullOrEmpty(powerShellExe, nameof(powerShellExe)); // Determine whether the script file is rooted. // TODO: If script file begins and ends with a double-quote, trim quotes before making determination. Likewise when determining whether the file exists. bool isScriptFileRooted = false; try { // Path.IsPathRooted throws if illegal characters are in the path. isScriptFileRooted = Path.IsPathRooted(scriptFile); } catch (Exception ex) { Trace.Info($"Unable to determine whether the script file is rooted: {ex.Message}"); } Trace.Info($"Script file is rooted: {isScriptFileRooted}"); // Determine the working directory. string workingDirectory; if (!string.IsNullOrEmpty(Data.WorkingDirectory)) { workingDirectory = Data.WorkingDirectory; } else { if (isScriptFileRooted && File.Exists(scriptFile)) { workingDirectory = Path.GetDirectoryName(scriptFile); } else { workingDirectory = Path.Combine(TaskDirectory, "DefaultTaskWorkingDirectory"); } } ExecutionContext.Debug($"Working directory: '{workingDirectory}'"); Directory.CreateDirectory(workingDirectory); // Invoke the process. ExecutionContext.Debug($"{powerShellExe} {powerShellExeArgs}"); ExecutionContext.Command(nestedExpression); using (var processInvoker = HostContext.CreateService <IProcessInvoker>()) { processInvoker.OutputDataReceived += OnOutputDataReceived; processInvoker.ErrorDataReceived += OnErrorDataReceived; int exitCode = await processInvoker.ExecuteAsync( workingDirectory : workingDirectory, fileName : powerShellExe, arguments : powerShellExeArgs, environment : Environment, cancellationToken : ExecutionContext.CancellationToken); FlushErrorData(); // Fail on error count. if (_failOnStandardError && _errorCount > 0) { if (ExecutionContext.Result != null) { Trace.Info($"Task result already set. Not failing due to error count ({_errorCount})."); } else { throw new Exception(StringUtil.Loc("ProcessCompletedWithCode0Errors1", exitCode, _errorCount)); } } // Fail on non-zero exit code. if (exitCode != 0) { throw new Exception(StringUtil.Loc("ProcessCompletedWithExitCode0", exitCode)); } } } finally { try { if (string.Equals(Data.ScriptType, InlineScriptType, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(scriptFile) && File.Exists(scriptFile)) { File.Delete(scriptFile); } } catch (Exception ex) { ExecutionContext.Warning(StringUtil.Loc("FailedToDeleteTempScript", scriptFile, ex.Message)); Trace.Error(ex); } } }
public async Task RunPluginTaskAsync(IExecutionContext context, string plugin, Dictionary <string, string> inputs, Dictionary <string, string> environment, Variables runtimeVariables, EventHandler <ProcessDataReceivedEventArgs> outputHandler) { ArgUtil.NotNullOrEmpty(plugin, nameof(plugin)); // Only allow plugins we defined if (!_taskPlugins.Contains(plugin)) { throw new NotSupportedException(plugin); } // Resolve the working directory. string workingDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); ArgUtil.Directory(workingDirectory, nameof(workingDirectory)); // Agent.PluginHost string file = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), $"Agent.PluginHost{Util.IOUtil.ExeExtension}"); ArgUtil.File(file, $"Agent.PluginHost{Util.IOUtil.ExeExtension}"); // Agent.PluginHost's arguments string arguments = $"task \"{plugin}\""; // construct plugin context var target = context.StepTarget(); Variables.TranslationMethod translateToHostPath = Variables.DefaultStringTranslator; ContainerInfo containerInfo = target as ContainerInfo; // Since plugins run on the host, but the inputs and variables have already been translated // to the container path, we need to convert them back to the host path // TODO: look to see if there is a better way to not have translate these back if (containerInfo != null) { var newInputs = new Dictionary <string, string>(); foreach (var entry in inputs) { newInputs[entry.Key] = containerInfo.TranslateToHostPath(entry.Value); } inputs = newInputs; translateToHostPath = (string val) => { return(containerInfo.TranslateToHostPath(val)); }; } AgentTaskPluginExecutionContext pluginContext = new AgentTaskPluginExecutionContext { Inputs = inputs, Repositories = context.Repositories, Endpoints = context.Endpoints, Container = containerInfo, //TODO: Figure out if this needs to have all the containers or just the one for the current step JobSettings = context.JobSettings, }; // variables runtimeVariables.CopyInto(pluginContext.Variables, translateToHostPath); context.TaskVariables.CopyInto(pluginContext.TaskVariables, translateToHostPath); using (var processInvoker = HostContext.CreateService <IProcessInvoker>()) { var redirectStandardIn = new InputQueue <string>(); redirectStandardIn.Enqueue(JsonUtility.ToString(pluginContext)); processInvoker.OutputDataReceived += outputHandler; processInvoker.ErrorDataReceived += outputHandler; // Execute the process. Exit code 0 should always be returned. // A non-zero exit code indicates infrastructural failure. // Task failure should be communicated over STDOUT using ## commands. await processInvoker.ExecuteAsync(workingDirectory : workingDirectory, fileName : file, arguments : arguments, environment : environment, requireExitCodeZero : true, outputEncoding : Encoding.UTF8, killProcessOnCancel : false, redirectStandardIn : redirectStandardIn, cancellationToken : context.CancellationToken); } }
public async Task RunAsync() { // Validate args. Trace.Entering(); ArgUtil.NotNull(Data, nameof(Data)); ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext)); ArgUtil.NotNull(Inputs, nameof(Inputs)); ArgUtil.Directory(TaskDirectory, nameof(TaskDirectory)); #if !OS_WINDOWS // Ensure compat vso-task-lib exist at the root of _work folder // This will make vsts-agent works against 2015 RTM/QU1 TFS, since tasks in those version doesn't package with task lib // Put the 0.5.5 version vso-task-lib into the root of _work/node_modules folder, so tasks are able to find those lib. if (!File.Exists(Path.Combine(IOUtil.GetWorkPath(HostContext), "node_modules", "vso-task-lib", "package.json"))) { string vsoTaskLibFromExternal = Path.Combine(IOUtil.GetExternalsPath(), "vso-task-lib"); string compatVsoTaskLibInWork = Path.Combine(IOUtil.GetWorkPath(HostContext), "node_modules", "vso-task-lib"); IOUtil.CopyDirectory(vsoTaskLibFromExternal, compatVsoTaskLibInWork, ExecutionContext.CancellationToken); } #endif // Update the env dictionary. AddInputsToEnvironment(); AddEndpointsToEnvironment(); AddSecureFilesToEnvironment(); AddVariablesToEnvironment(); AddIntraTaskStatesToEnvironment(); // Resolve the target script. string target = Data.Target; ArgUtil.NotNullOrEmpty(target, nameof(target)); target = Path.Combine(TaskDirectory, target); ArgUtil.File(target, nameof(target)); // Resolve the working directory. string workingDirectory = Data.WorkingDirectory; if (string.IsNullOrEmpty(workingDirectory)) { if (!string.IsNullOrEmpty(ExecutionContext.Variables.System_DefaultWorkingDirectory)) { workingDirectory = ExecutionContext.Variables.System_DefaultWorkingDirectory; } else { workingDirectory = ExecutionContext.Variables.Agent_WorkFolder; } } ArgUtil.Directory(workingDirectory, nameof(workingDirectory)); // Setup the process invoker. using (var processInvoker = HostContext.CreateService <IProcessInvoker>()) { processInvoker.OutputDataReceived += OnDataReceived; processInvoker.ErrorDataReceived += OnDataReceived; string node = Path.Combine( IOUtil.GetExternalsPath(), "node", "bin", $"node{IOUtil.ExeExtension}"); // Format the arguments passed to node. // 1) Wrap the script file path in double quotes. // 2) Escape double quotes within the script file path. Double-quote is a valid // file name character on Linux. string arguments = StringUtil.Format(@"""{0}""", target.Replace(@"""", @"\""")); #if OS_WINDOWS // It appears that node.exe outputs UTF8 when not in TTY mode. Encoding outputEncoding = Encoding.UTF8; #else // Let .NET choose the default. Encoding outputEncoding = null; #endif // Execute the process. Exit code 0 should always be returned. // A non-zero exit code indicates infrastructural failure. // Task failure should be communicated over STDOUT using ## commands. await processInvoker.ExecuteAsync( workingDirectory : workingDirectory, fileName : node, arguments : arguments, environment : Environment, requireExitCodeZero : true, outputEncoding : outputEncoding, cancellationToken : ExecutionContext.CancellationToken); } }
public async Task RunPluginActionAsync(IExecutionContext context, string plugin, Dictionary <string, string> inputs, Dictionary <string, string> environment, Variables runtimeVariables, EventHandler <ProcessDataReceivedEventArgs> outputHandler) { ArgUtil.NotNullOrEmpty(plugin, nameof(plugin)); // Only allow plugins we defined if (!_actionPlugins.Any(x => x.Value.PluginTypeName == plugin || x.Value.PostPluginTypeName == plugin)) { throw new NotSupportedException(plugin); } // Resolve the working directory. string workingDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); ArgUtil.Directory(workingDirectory, nameof(workingDirectory)); // Runner.PluginHost string file = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), $"Runner.PluginHost{IOUtil.ExeExtension}"); ArgUtil.File(file, $"Runner.PluginHost{IOUtil.ExeExtension}"); // Runner.PluginHost's arguments string arguments = $"action \"{plugin}\""; // construct plugin context RunnerActionPluginExecutionContext pluginContext = new RunnerActionPluginExecutionContext { Inputs = inputs, Endpoints = context.Global.Endpoints, Context = context.ExpressionValues }; // variables foreach (var variable in context.Global.Variables.AllVariables) { pluginContext.Variables[variable.Name] = new VariableValue(variable.Value, variable.Secret); } using (var processInvoker = HostContext.CreateService <IProcessInvoker>()) { var redirectStandardIn = Channel.CreateUnbounded <string>(new UnboundedChannelOptions() { SingleReader = true, SingleWriter = true }); redirectStandardIn.Writer.TryWrite(JsonUtility.ToString(pluginContext)); processInvoker.OutputDataReceived += outputHandler; processInvoker.ErrorDataReceived += outputHandler; // Execute the process. Exit code 0 should always be returned. // A non-zero exit code indicates infrastructural failure. // Task failure should be communicated over STDOUT using ## commands. await processInvoker.ExecuteAsync(workingDirectory : workingDirectory, fileName : file, arguments : arguments, environment : environment, requireExitCodeZero : true, outputEncoding : Encoding.UTF8, killProcessOnCancel : false, redirectStandardIn : redirectStandardIn, cancellationToken : context.CancellationToken); } }
public Task StartAsync(IExecutionContext context, List <IStep> steps, CancellationToken token) { Trace.Entering(); ArgUtil.NotNull(context, nameof(context)); List <PluginInfo> enabledPlugins = new List <PluginInfo>(); if (context.Variables.GetBoolean("agent.disablelogplugin") ?? false) { // all log plugs are disabled context.Debug("All log plugins are disabled."); } else { foreach (var plugin in _logPlugins) { if (context.Variables.GetBoolean($"agent.disablelogplugin.{plugin.Key}") ?? false) { // skip plugin context.Debug($"Log plugin '{plugin.Key}' is disabled."); continue; } else { enabledPlugins.Add(plugin.Value); } } } if (enabledPlugins.Count > 0) { // Resolve the working directory. string workingDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); ArgUtil.Directory(workingDirectory, nameof(workingDirectory)); // Agent.PluginHost string file = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), $"Agent.PluginHost{Util.IOUtil.ExeExtension}"); ArgUtil.File(file, $"Agent.PluginHost{Util.IOUtil.ExeExtension}"); // Agent.PluginHost's arguments string arguments = $"log \"{_instanceId.ToString("D")}\""; var processInvoker = HostContext.CreateService <IProcessInvoker>(); processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs e) => { if (!string.IsNullOrEmpty(e.Data)) { _outputs.Enqueue(e.Data); } }; processInvoker.ErrorDataReceived += (object sender, ProcessDataReceivedEventArgs e) => { if (!string.IsNullOrEmpty(e.Data)) { _outputs.Enqueue(e.Data); } }; _pluginHostProcess = processInvoker.ExecuteAsync(workingDirectory: workingDirectory, fileName: file, arguments: arguments, environment: null, requireExitCodeZero: true, outputEncoding: Encoding.UTF8, killProcessOnCancel: true, redirectStandardIn: _redirectedStdin, cancellationToken: token); // construct plugin context AgentLogPluginHostContext pluginContext = new AgentLogPluginHostContext { PluginAssemblies = new List <string>(), Repositories = context.Repositories, Endpoints = context.Endpoints, Variables = new Dictionary <string, VariableValue>(), Steps = new Dictionary <string, Pipelines.TaskStepDefinitionReference>() }; // plugins pluginContext.PluginAssemblies.AddRange(_logPlugins.Values.Select(x => x.AssemblyName)); // variables foreach (var publicVar in context.Variables.Public) { pluginContext.Variables[publicVar.Key] = publicVar.Value; } foreach (var privateVar in context.Variables.Private) { pluginContext.Variables[privateVar.Key] = new VariableValue(privateVar.Value, true); } // steps foreach (var step in steps) { var taskStep = step as ITaskRunner; if (taskStep != null) { pluginContext.Steps[taskStep.ExecutionContext.Id.ToString("D")] = taskStep.Task.Reference; } } Trace.Info("Send serialized context through STDIN"); _redirectedStdin.Enqueue(JsonUtility.ToString(pluginContext)); foreach (var plugin in _logPlugins) { context.Output($"Plugin: '{plugin.Value.FriendlyName}' is running in background."); } } return(Task.CompletedTask); }
public async Task RunAsync() { // Validate args. Trace.Entering(); ArgUtil.NotNull(Data, nameof(Data)); ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext)); ArgUtil.NotNull(Inputs, nameof(Inputs)); ArgUtil.Directory(TaskDirectory, nameof(TaskDirectory)); // Resolve the target script. ArgUtil.NotNullOrEmpty(Data.Target, nameof(Data.Target)); string scriptFile = Path.Combine(TaskDirectory, Data.Target); ArgUtil.File(scriptFile, nameof(scriptFile)); // Determine the working directory. string workingDirectory = Data.WorkingDirectory; if (String.IsNullOrEmpty(workingDirectory)) { workingDirectory = Path.GetDirectoryName(scriptFile); } else { if (!Directory.Exists(workingDirectory)) { Directory.CreateDirectory(workingDirectory); } } // scriptName AddEnvironmentVariable("VSTSPSHOSTSCRIPTNAME", scriptFile); // workingFolder AddEnvironmentVariable("VSTSPSHOSTWORKINGFOLDER", workingDirectory); // outputPreference AddEnvironmentVariable("VSTSPSHOSTOUTPUTPREFER", ExecutionContext.WriteDebug ? "Continue" : "SilentlyContinue"); // inputParameters if (Inputs.Count > 0) { AddEnvironmentVariable("VSTSPSHOSTINPUTPARAMETER", JsonUtility.ToString(Inputs)); } List <String> arguments = new List <string>(); Dictionary <String, String> argumentParameters = new Dictionary <String, String>(); if (string.IsNullOrEmpty(Data.ArgumentFormat)) { // treatInputsAsArguments AddEnvironmentVariable("VSTSPSHOSTINPUTISARG", "True"); } else { MatchCollection matches = _argumentMatching.Matches(Data.ArgumentFormat); if (matches[0].Value.StartsWith("-")) { String currentKey = String.Empty; foreach (Match match in matches) { if (match.Value.StartsWith("-")) { currentKey = match.Value.Trim('-'); argumentParameters.Add(currentKey, String.Empty); } else if (!match.Value.StartsWith("-") && !String.IsNullOrEmpty(currentKey)) { argumentParameters[currentKey] = match.Value; currentKey = String.Empty; } else { throw new Exception($"Found value {match.Value} with no corresponding named parameter"); } } } else { foreach (Match match in matches) { arguments.Add(match.Value); } } // arguments if (arguments.Count > 0) { AddEnvironmentVariable("VSTSPSHOSTARGS", JsonUtility.ToString(arguments)); } // argumentParameters if (argumentParameters.Count > 0) { AddEnvironmentVariable("VSTSPSHOSTARGPARAMETER", JsonUtility.ToString(argumentParameters)); } } // push all variable. foreach (var variable in ExecutionContext.Variables.Public.Concat(ExecutionContext.Variables.Private)) { AddEnvironmentVariable("VSTSPSHOSTVAR_" + variable.Key, variable.Value); } // push all public variable. foreach (var variable in ExecutionContext.Variables.Public) { AddEnvironmentVariable("VSTSPSHOSTPUBVAR_" + variable.Key, variable.Value); } // push all endpoints List <String> ids = new List <string>(); foreach (ServiceEndpoint endpoint in ExecutionContext.Endpoints) { string partialKey = null; if (string.Equals(endpoint.Name, ServiceEndpoints.SystemVssConnection, StringComparison.OrdinalIgnoreCase)) { partialKey = ServiceEndpoints.SystemVssConnection.ToUpperInvariant(); AddEnvironmentVariable("VSTSPSHOSTSYSTEMENDPOINT_URL", endpoint.Url.ToString()); AddEnvironmentVariable("VSTSPSHOSTSYSTEMENDPOINT_AUTH", JsonUtility.ToString(endpoint.Authorization)); } else { if (endpoint.Id == Guid.Empty && endpoint.Data.ContainsKey("repositoryId")) { partialKey = endpoint.Data["repositoryId"].ToUpperInvariant(); } else { partialKey = endpoint.Id.ToString("D").ToUpperInvariant(); } ids.Add(partialKey); AddEnvironmentVariable("VSTSPSHOSTENDPOINT_URL_" + partialKey, endpoint.Url.ToString()); AddEnvironmentVariable("VSTSPSHOSTENDPOINT_NAME_" + partialKey, endpoint.Name); AddEnvironmentVariable("VSTSPSHOSTENDPOINT_TYPE_" + partialKey, endpoint.Type); AddEnvironmentVariable("VSTSPSHOSTENDPOINT_AUTH_" + partialKey, JsonUtility.ToString(endpoint.Authorization)); AddEnvironmentVariable("VSTSPSHOSTENDPOINT_DATA_" + partialKey, JsonUtility.ToString(endpoint.Data)); } } if (ids.Count > 0) { AddEnvironmentVariable("VSTSPSHOSTENDPOINT_IDS", JsonUtility.ToString(ids)); } // Invoke the process. using (var processInvoker = HostContext.CreateService <IProcessInvoker>()) { processInvoker.OutputDataReceived += OnDataReceived; processInvoker.ErrorDataReceived += OnDataReceived; try { String vstsPSHostExe = Path.Combine(IOUtil.GetExternalsPath(), "vstshost", "LegacyVSTSPowerShellHost.exe"); Int32 exitCode = await processInvoker.ExecuteAsync(workingDirectory : workingDirectory, fileName : vstsPSHostExe, arguments : "", environment : Environment, cancellationToken : ExecutionContext.CancellationToken); // the exit code from vstsPSHost.exe indicate how many error record we get during execution // -1 exit code means infrastructure failure of Host itself. // this is to match current handler's logic. if (exitCode > 0) { if (ExecutionContext.Result != null) { ExecutionContext.Debug($"Task result already set. Not failing due to error count ({exitCode})."); } else { // We fail task and add issue. ExecutionContext.Result = TaskResult.Failed; ExecutionContext.Error(StringUtil.Loc("PSScriptError", exitCode)); } } else if (exitCode < 0) { // We fail task and add issue. ExecutionContext.Result = TaskResult.Failed; ExecutionContext.Error(StringUtil.Loc("VSTSHostNonZeroReturn", exitCode)); } } finally { processInvoker.OutputDataReceived -= OnDataReceived; processInvoker.ErrorDataReceived -= OnDataReceived; } } }
public async Task RunAsync() { // Validate args. Trace.Entering(); ArgUtil.NotNull(Data, nameof(Data)); ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext)); ArgUtil.NotNull(Inputs, nameof(Inputs)); ArgUtil.Directory(TaskDirectory, nameof(TaskDirectory)); #if !OS_WINDOWS // Ensure compat vso-task-lib exist at the root of _work folder // This will make vsts-agent works against 2015 RTM/QU1 TFS, since tasks in those version doesn't package with task lib // Put the 0.5.5 version vso-task-lib into the root of _work/node_modules folder, so tasks are able to find those lib. if (!File.Exists(Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), "node_modules", "vso-task-lib", "package.json"))) { string vsoTaskLibFromExternal = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), "vso-task-lib"); string compatVsoTaskLibInWork = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), "node_modules", "vso-task-lib"); IOUtil.CopyDirectory(vsoTaskLibFromExternal, compatVsoTaskLibInWork, ExecutionContext.CancellationToken); } #endif // Update the env dictionary. AddInputsToEnvironment(); AddEndpointsToEnvironment(); AddSecureFilesToEnvironment(); AddVariablesToEnvironment(); AddTaskVariablesToEnvironment(); AddPrependPathToEnvironment(); // Resolve the target script. string target = Data.Target; ArgUtil.NotNullOrEmpty(target, nameof(target)); target = Path.Combine(TaskDirectory, target); ArgUtil.File(target, nameof(target)); // Resolve the working directory. string workingDirectory = Data.WorkingDirectory; if (string.IsNullOrEmpty(workingDirectory)) { if (!string.IsNullOrEmpty(ExecutionContext.Variables.System_DefaultWorkingDirectory)) { workingDirectory = ExecutionContext.Variables.System_DefaultWorkingDirectory; } else { workingDirectory = ExecutionContext.Variables.Agent_WorkFolder; } } ArgUtil.Directory(workingDirectory, nameof(workingDirectory)); // fix vsts-task-lib for node 6.x // vsts-task-lib 0.6/0.7/0.8/0.9/2.0-preview implemented String.prototype.startsWith and String.prototype.endsWith since Node 5.x doesn't have them. // however the implementation is added in node 6.x, the implementation in vsts-task-lib is different. // node 6.x's implementation takes 2 parameters str.endsWith(searchString[, length]) / str.startsWith(searchString[, length]) // the implementation vsts-task-lib had only takes one parameter str.endsWith(searchString) / str.startsWith(searchString). // as long as vsts-task-lib be loaded into memory, it will overwrite the implementation node 6.x has, // so any scirpt that use the second parameter (length) will encounter unexpected result. // to avoid customer hit this error, we will modify the file (extensions.js) under vsts-task-lib module folder when customer choose to use Node 6.x Trace.Info("Inspect node_modules folder, make sure vsts-task-lib doesn't overwrite String.startsWith/endsWith."); FixVstsTaskLibModule(); StepHost.OutputDataReceived += OnDataReceived; StepHost.ErrorDataReceived += OnDataReceived; string file = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), "node", "bin", $"node{IOUtil.ExeExtension}"); // Format the arguments passed to node. // 1) Wrap the script file path in double quotes. // 2) Escape double quotes within the script file path. Double-quote is a valid // file name character on Linux. string arguments = StepHost.ResolvePathForStepHost(StringUtil.Format(@"""{0}""", target.Replace(@"""", @"\"""))); #if OS_WINDOWS // It appears that node.exe outputs UTF8 when not in TTY mode. Encoding outputEncoding = Encoding.UTF8; #else // Let .NET choose the default. Encoding outputEncoding = null; #endif // Execute the process. Exit code 0 should always be returned. // A non-zero exit code indicates infrastructural failure. // Task failure should be communicated over STDOUT using ## commands. await StepHost.ExecuteAsync(workingDirectory : StepHost.ResolvePathForStepHost(workingDirectory), fileName : StepHost.ResolvePathForStepHost(file), arguments : arguments, environment : Environment, requireExitCodeZero : true, outputEncoding : outputEncoding, killProcessOnCancel : false, cancellationToken : ExecutionContext.CancellationToken); }
public async Task RunAsync() { // Validate args. Trace.Entering(); ArgUtil.NotNull(Data, nameof(Data)); ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext)); ArgUtil.NotNull(Inputs, nameof(Inputs)); ArgUtil.Directory(TaskDirectory, nameof(TaskDirectory)); if (!PlatformUtil.RunningOnWindows) { // Ensure compat vso-task-lib exist at the root of _work folder // This will make vsts-agent work against 2015 RTM/QU1 TFS, since tasks in those version doesn't package with task lib // Put the 0.5.5 version vso-task-lib into the root of _work/node_modules folder, so tasks are able to find those lib. if (!File.Exists(Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), "node_modules", "vso-task-lib", "package.json"))) { string vsoTaskLibFromExternal = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), "vso-task-lib"); string compatVsoTaskLibInWork = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), "node_modules", "vso-task-lib"); IOUtil.CopyDirectory(vsoTaskLibFromExternal, compatVsoTaskLibInWork, ExecutionContext.CancellationToken); } } // Update the env dictionary. AddInputsToEnvironment(); AddEndpointsToEnvironment(); AddSecureFilesToEnvironment(); AddVariablesToEnvironment(); AddTaskVariablesToEnvironment(); AddPrependPathToEnvironment(); // Resolve the target script. string target = Data.Target; ArgUtil.NotNullOrEmpty(target, nameof(target)); target = Path.Combine(TaskDirectory, target); ArgUtil.File(target, nameof(target)); // Resolve the working directory. string workingDirectory = Data.WorkingDirectory; if (string.IsNullOrEmpty(workingDirectory)) { workingDirectory = ExecutionContext.Variables.Get(Constants.Variables.System.DefaultWorkingDirectory); if (string.IsNullOrEmpty(workingDirectory)) { workingDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); } } // fix vsts-task-lib for node 6.x // vsts-task-lib 0.6/0.7/0.8/0.9/2.0-preview implemented String.prototype.startsWith and String.prototype.endsWith since Node 5.x doesn't have them. // however the implementation is added in node 6.x, the implementation in vsts-task-lib is different. // node 6.x's implementation takes 2 parameters str.endsWith(searchString[, length]) / str.startsWith(searchString[, length]) // the implementation vsts-task-lib had only takes one parameter str.endsWith(searchString) / str.startsWith(searchString). // as long as vsts-task-lib be loaded into memory, it will overwrite the implementation node 6.x has, // so any script that use the second parameter (length) will encounter unexpected result. // to avoid customer hit this error, we will modify the file (extensions.js) under vsts-task-lib module folder when customer choose to use Node 6.x Trace.Info("Inspect node_modules folder, make sure vsts-task-lib doesn't overwrite String.startsWith/endsWith."); FixVstsTaskLibModule(); StepHost.OutputDataReceived += OnDataReceived; StepHost.ErrorDataReceived += OnDataReceived; string file; if (!string.IsNullOrEmpty(ExecutionContext.StepTarget()?.CustomNodePath)) { file = ExecutionContext.StepTarget().CustomNodePath; } else { file = GetNodeLocation(); ExecutionContext.Debug("Using node path: " + file); if (!File.Exists(file)) { throw new FileNotFoundException(StringUtil.Loc("MissingNodePath", file)); } } // Format the arguments passed to node. // 1) Wrap the script file path in double quotes. // 2) Escape double quotes within the script file path. Double-quote is a valid // file name character on Linux. string arguments = StepHost.ResolvePathForStepHost(StringUtil.Format(@"""{0}""", target.Replace(@"""", @"\"""))); // Let .NET choose the default, except on Windows. Encoding outputEncoding = null; if (PlatformUtil.RunningOnWindows) { // It appears that node.exe outputs UTF8 when not in TTY mode. outputEncoding = Encoding.UTF8; } try { // Execute the process. Exit code 0 should always be returned. // A non-zero exit code indicates infrastructural failure. // Task failure should be communicated over STDOUT using ## commands. Task step = StepHost.ExecuteAsync(workingDirectory: StepHost.ResolvePathForStepHost(workingDirectory), fileName: StepHost.ResolvePathForStepHost(file), arguments: arguments, environment: Environment, requireExitCodeZero: true, outputEncoding: outputEncoding, killProcessOnCancel: false, inheritConsoleHandler: !ExecutionContext.Variables.Retain_Default_Encoding, cancellationToken: ExecutionContext.CancellationToken); // Wait for either the node exit or force finish through ##vso command await System.Threading.Tasks.Task.WhenAny(step, ExecutionContext.ForceCompleted); if (ExecutionContext.ForceCompleted.IsCompleted) { ExecutionContext.Debug("The task was marked as \"done\", but the process has not closed after 5 seconds. Treating the task as complete."); } else { await step; } } finally { StepHost.OutputDataReceived -= OnDataReceived; StepHost.ErrorDataReceived -= OnDataReceived; } }