Esempio n. 1
0
        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);
            }
        }
Esempio n. 2
0
        // 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);
        }
Esempio n. 3
0
        // 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);
            }
        }
Esempio n. 5
0
        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);
            }
        }
Esempio n. 6
0
        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);
        }
Esempio n. 7
0
        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);
        }
Esempio n. 8
0
        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);
        }
Esempio n. 9
0
        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);
                }
        }
Esempio n. 10
0
        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]");
            }
        }
Esempio n. 11
0
#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]");
            }
        }
Esempio n. 14
0
        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.");
        }
Esempio n. 15
0
        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;
                        }
                    }
                }
        }
Esempio n. 18
0
        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;
                }
            }
        }
Esempio n. 19
0
        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);
        }
Esempio n. 20
0
        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);
            }
        }
Esempio n. 21
0
        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);
                }
            }
        }
Esempio n. 22
0
        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);
            }
        }
Esempio n. 23
0
        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);
            }
        }
Esempio n. 24
0
        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);
        }
Esempio n. 26
0
        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;
                }
            }
        }
Esempio n. 27
0
        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);
        }
Esempio n. 28
0
        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;
            }
        }