Ejemplo n.º 1
0
        public async Task RunAsync()
        {
            // Validate args.
            Trace.Entering();
            ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext));
            ArgUtil.NotNull(ExecutionContext.Variables, nameof(ExecutionContext.Variables));
            ArgUtil.NotNull(Task, nameof(Task));
            var taskManager    = HostContext.GetService <ITaskManager>();
            var handlerFactory = HostContext.GetService <IHandlerFactory>();

            // Set the task id and display name variable.
            using (var scope = ExecutionContext.Variables.CreateScope())
            {
                scope.Set(Constants.Variables.Task.DisplayName, DisplayName);
                scope.Set(WellKnownDistributedTaskVariables.TaskInstanceId, Task.Id.ToString("D"));
                scope.Set(WellKnownDistributedTaskVariables.TaskDisplayName, DisplayName);
                scope.Set(WellKnownDistributedTaskVariables.TaskInstanceName, Task.Name);

                // Load the task definition and choose the handler.
                // TODO: Add a try catch here to give a better error message.
                Definition definition = taskManager.Load(Task);
                ArgUtil.NotNull(definition, nameof(definition));

                // Verify Signatures and Re-Extract Tasks if neccessary
                await VerifyTask(taskManager, definition);

                // Print out task metadata
                PrintTaskMetaData(definition);

                ExecutionData currentExecution = null;
                switch (Stage)
                {
                case JobRunStage.PreJob:
                    currentExecution = definition.Data?.PreJobExecution;
                    break;

                case JobRunStage.Main:
                    currentExecution = definition.Data?.Execution;
                    break;

                case JobRunStage.PostJob:
                    currentExecution = definition.Data?.PostJobExecution;
                    break;
                }
                ;

                HandlerData handlerData = GetHandlerData(ExecutionContext, currentExecution, PlatformUtil.HostOS);

                if (handlerData == null)
                {
                    if (PlatformUtil.RunningOnWindows)
                    {
                        throw new InvalidOperationException(StringUtil.Loc("SupportedTaskHandlerNotFoundWindows", $"{PlatformUtil.HostOS}({PlatformUtil.HostArchitecture})"));
                    }

                    throw new InvalidOperationException(StringUtil.Loc("SupportedTaskHandlerNotFoundLinux"));
                }
                Trace.Info($"Handler data is of type {handlerData}");

                Variables runtimeVariables = ExecutionContext.Variables;
                IStepHost stepHost         = HostContext.CreateService <IDefaultStepHost>();
                var       stepTarget       = ExecutionContext.StepTarget();
                // Setup container stephost and the right runtime variables for running job inside container.
                if (stepTarget is ContainerInfo containerTarget)
                {
                    if (Stage == JobRunStage.PostJob &&
                        AgentKnobs.SkipPostExeceutionIfTargetContainerStopped.GetValue(ExecutionContext).AsBoolean())
                    {
                        try
                        {
                            // Check that the target container is still running, if not Skip task execution
                            IDockerCommandManager dockerManager = HostContext.GetService <IDockerCommandManager>();
                            bool isContainerRunning             = await dockerManager.IsContainerRunning(ExecutionContext, containerTarget.ContainerId);

                            if (!isContainerRunning)
                            {
                                ExecutionContext.Result     = TaskResult.Skipped;
                                ExecutionContext.ResultCode = $"Target container - {containerTarget.ContainerName} has been stopped, task post-execution will be skipped";
                                return;
                            }
                        }
                        catch (Exception ex)
                        {
                            ExecutionContext.Write(WellKnownTags.Warning, $"Failed to check container state for task post-execution. Exception: {ex}");
                        }
                    }

                    if (handlerData is AgentPluginHandlerData)
                    {
                        // plugin handler always runs on the Host, the runtime variables needs to the variable works on the Host, ex: file path variable System.DefaultWorkingDirectory
                        Dictionary <string, VariableValue> variableCopy = new Dictionary <string, VariableValue>(StringComparer.OrdinalIgnoreCase);
                        foreach (var publicVar in ExecutionContext.Variables.Public)
                        {
                            variableCopy[publicVar.Key] = new VariableValue(stepTarget.TranslateToHostPath(publicVar.Value));
                        }
                        foreach (var secretVar in ExecutionContext.Variables.Private)
                        {
                            variableCopy[secretVar.Key] = new VariableValue(stepTarget.TranslateToHostPath(secretVar.Value), true);
                        }

                        List <string> expansionWarnings;
                        runtimeVariables = new Variables(HostContext, variableCopy, out expansionWarnings);
                        expansionWarnings?.ForEach(x => ExecutionContext.Warning(x));
                    }
                    else if (handlerData is BaseNodeHandlerData || handlerData is PowerShell3HandlerData)
                    {
                        // Only the node, node10, and powershell3 handlers support running inside container.
                        // Make sure required container is already created.
                        ArgUtil.NotNullOrEmpty(containerTarget.ContainerId, nameof(containerTarget.ContainerId));
                        var containerStepHost = HostContext.CreateService <IContainerStepHost>();
                        containerStepHost.Container = containerTarget;
                        stepHost = containerStepHost;
                    }
                    else
                    {
                        throw new NotSupportedException(String.Format("Task '{0}' is using legacy execution handler '{1}' which is not supported in container execution flow.", definition.Data.FriendlyName, handlerData.GetType().ToString()));
                    }
                }

                // Load the default input values from the definition.
                Trace.Verbose("Loading default inputs.");
                var inputs = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase);
                foreach (var input in (definition.Data?.Inputs ?? new TaskInputDefinition[0]))
                {
                    string key = input?.Name?.Trim() ?? string.Empty;
                    if (!string.IsNullOrEmpty(key))
                    {
                        if (AgentKnobs.DisableInputTrimming.GetValue(ExecutionContext).AsBoolean())
                        {
                            inputs[key] = input.DefaultValue ?? string.Empty;
                        }
                        else
                        {
                            inputs[key] = input.DefaultValue?.Trim() ?? string.Empty;
                        }
                    }
                }

                // Merge the instance inputs.
                Trace.Verbose("Loading instance inputs.");
                foreach (var input in (Task.Inputs as IEnumerable <KeyValuePair <string, string> > ?? new KeyValuePair <string, string> [0]))
                {
                    string key = input.Key?.Trim() ?? string.Empty;
                    if (!string.IsNullOrEmpty(key))
                    {
                        if (AgentKnobs.DisableInputTrimming.GetValue(ExecutionContext).AsBoolean())
                        {
                            inputs[key] = input.Value ?? string.Empty;
                        }
                        else
                        {
                            inputs[key] = input.Value?.Trim() ?? string.Empty;
                        }
                    }
                }

                // Expand the inputs.
                Trace.Verbose("Expanding inputs.");
                runtimeVariables.ExpandValues(target: inputs);

                // We need to verify inputs of the tasks that were injected by decorators, to check if they contain secrets,
                // for security reasons execution of tasks in this case should be skipped.
                // Target task inputs could be injected into the decorator's tasks if the decorator has post-task-tasks or pre-task-tasks targets,
                // such tasks will have names that start with __system_pretargettask_ or __system_posttargettask_.
                var taskDecoratorManager = HostContext.GetService <ITaskDecoratorManager>();
                if (taskDecoratorManager.IsInjectedTaskForTarget(Task.Name) &&
                    taskDecoratorManager.IsInjectedInputsContainsSecrets(inputs, out var inputsWithSecrets))
                {
                    var inputsForReport = taskDecoratorManager.GenerateTaskResultMessage(inputsWithSecrets);

                    ExecutionContext.Result     = TaskResult.Skipped;
                    ExecutionContext.ResultCode = StringUtil.Loc("SecretsAreNotAllowedInInjectedTaskInputs", inputsForReport);
                    return;
                }

                VarUtil.ExpandEnvironmentVariables(HostContext, target: inputs);

                // Translate the server file path inputs to local paths.
                foreach (var input in definition.Data?.Inputs ?? new TaskInputDefinition[0])
                {
                    if (string.Equals(input.InputType, TaskInputType.FilePath, StringComparison.OrdinalIgnoreCase))
                    {
                        Trace.Verbose($"Translating file path input '{input.Name}': '{inputs[input.Name]}'");
                        inputs[input.Name] = stepHost.ResolvePathForStepHost(TranslateFilePathInput(inputs[input.Name] ?? string.Empty));
                        Trace.Verbose($"Translated file path input '{input.Name}': '{inputs[input.Name]}'");
                    }
                }

                // Load the task environment.
                Trace.Verbose("Loading task environment.");
                var environment = new Dictionary <string, string>(VarUtil.EnvironmentVariableKeyComparer);
                foreach (var env in (Task.Environment ?? new Dictionary <string, string>(0)))
                {
                    string key = env.Key?.Trim() ?? string.Empty;
                    if (!string.IsNullOrEmpty(key))
                    {
                        environment[key] = env.Value?.Trim() ?? string.Empty;
                    }
                }

                // Expand the inputs.
                Trace.Verbose("Expanding task environment.");
                runtimeVariables.ExpandValues(target: environment);
                VarUtil.ExpandEnvironmentVariables(HostContext, target: environment);

                // Expand the handler inputs.
                Trace.Verbose("Expanding handler inputs.");
                VarUtil.ExpandValues(HostContext, source: inputs, target: handlerData.Inputs);
                runtimeVariables.ExpandValues(target: handlerData.Inputs);

                // Get each endpoint ID referenced by the task.
                var endpointIds = new List <Guid>();
                foreach (var input in definition.Data?.Inputs ?? new TaskInputDefinition[0])
                {
                    if ((input.InputType ?? string.Empty).StartsWith("connectedService:", StringComparison.OrdinalIgnoreCase))
                    {
                        string inputKey = input?.Name?.Trim() ?? string.Empty;
                        string inputValue;
                        if (!string.IsNullOrEmpty(inputKey) &&
                            inputs.TryGetValue(inputKey, out inputValue) &&
                            !string.IsNullOrEmpty(inputValue))
                        {
                            foreach (string rawId in inputValue.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
                            {
                                Guid parsedId;
                                if (Guid.TryParse(rawId.Trim(), out parsedId) && parsedId != Guid.Empty)
                                {
                                    endpointIds.Add(parsedId);
                                }
                            }
                        }
                    }
                }

                if (endpointIds.Count > 0 &&
                    (runtimeVariables.GetBoolean(WellKnownDistributedTaskVariables.RestrictSecrets) ?? false) &&
                    (runtimeVariables.GetBoolean(Microsoft.TeamFoundation.Build.WebApi.BuildVariables.IsFork) ?? false))
                {
                    ExecutionContext.Result     = TaskResult.Skipped;
                    ExecutionContext.ResultCode = $"References service endpoint. PRs from repository forks are not allowed to access secrets in the pipeline. For more information see https://go.microsoft.com/fwlink/?linkid=862029 ";
                    return;
                }

                // Get the endpoints referenced by the task.
                var endpoints = (ExecutionContext.Endpoints ?? new List <ServiceEndpoint>(0))
                                .Join(inner: endpointIds,
                                      outerKeySelector: (ServiceEndpoint endpoint) => endpoint.Id,
                                      innerKeySelector: (Guid endpointId) => endpointId,
                                      resultSelector: (ServiceEndpoint endpoint, Guid endpointId) => endpoint)
                                .ToList();

                // Add the system endpoint.
                foreach (ServiceEndpoint endpoint in (ExecutionContext.Endpoints ?? new List <ServiceEndpoint>(0)))
                {
                    if (string.Equals(endpoint.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase))
                    {
                        endpoints.Add(endpoint);
                        break;
                    }
                }

                // Get each secure file ID referenced by the task.
                var secureFileIds = new List <Guid>();
                foreach (var input in definition.Data?.Inputs ?? new TaskInputDefinition[0])
                {
                    if (string.Equals(input.InputType ?? string.Empty, "secureFile", StringComparison.OrdinalIgnoreCase))
                    {
                        string inputKey = input?.Name?.Trim() ?? string.Empty;
                        string inputValue;
                        if (!string.IsNullOrEmpty(inputKey) &&
                            inputs.TryGetValue(inputKey, out inputValue) &&
                            !string.IsNullOrEmpty(inputValue))
                        {
                            foreach (string rawId in inputValue.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
                            {
                                Guid parsedId;
                                if (Guid.TryParse(rawId.Trim(), out parsedId) && parsedId != Guid.Empty)
                                {
                                    secureFileIds.Add(parsedId);
                                }
                            }
                        }
                    }
                }

                if (secureFileIds.Count > 0 &&
                    (runtimeVariables.GetBoolean(WellKnownDistributedTaskVariables.RestrictSecrets) ?? false) &&
                    (runtimeVariables.GetBoolean(Microsoft.TeamFoundation.Build.WebApi.BuildVariables.IsFork) ?? false))
                {
                    ExecutionContext.Result     = TaskResult.Skipped;
                    ExecutionContext.ResultCode = $"References secure file. PRs from repository forks are not allowed to access secrets in the pipeline. For more information see https://go.microsoft.com/fwlink/?linkid=862029";
                    return;
                }

                // Get the endpoints referenced by the task.
                var secureFiles = (ExecutionContext.SecureFiles ?? new List <SecureFile>(0))
                                  .Join(inner: secureFileIds,
                                        outerKeySelector: (SecureFile secureFile) => secureFile.Id,
                                        innerKeySelector: (Guid secureFileId) => secureFileId,
                                        resultSelector: (SecureFile secureFile, Guid secureFileId) => secureFile)
                                  .ToList();

                // Set output variables.
                foreach (var outputVar in definition.Data?.OutputVariables ?? new OutputVariable[0])
                {
                    if (outputVar != null && !string.IsNullOrEmpty(outputVar.Name))
                    {
                        ExecutionContext.OutputVariables.Add(outputVar.Name);
                    }
                }

                // translate inputs
                inputs = inputs.ToDictionary(kvp => kvp.Key, kvp => ExecutionContext.TranslatePathForStepTarget(kvp.Value));

                // Create the handler.
                IHandler handler = handlerFactory.Create(
                    ExecutionContext,
                    Task.Reference,
                    stepHost,
                    endpoints,
                    secureFiles,
                    handlerData,
                    inputs,
                    environment,
                    runtimeVariables,
                    taskDirectory: definition.Directory);

                // Run the task.
                int retryCount = this.Task.RetryCountOnTaskFailure;

                if (retryCount > 0)
                {
                    if (retryCount > RetryCountOnTaskFailureLimit)
                    {
                        ExecutionContext.Warning(StringUtil.Loc("RetryCountLimitExceeded", RetryCountOnTaskFailureLimit, retryCount));
                        retryCount = RetryCountOnTaskFailureLimit;
                    }

                    RetryHelper rh = new RetryHelper(ExecutionContext, retryCount);
                    await rh.RetryStep(async() => await handler.RunAsync(), RetryHelper.ExponentialDelay);
                }
                else
                {
                    await handler.RunAsync();
                }
            }
        }