feat: Enhance Task Runner with simulation and failure policy support
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Added tests for output projection and failure policy population in TaskPackPlanner. - Introduced new failure policy manifest in TestManifests. - Implemented simulation endpoints in the web service for task execution. - Created TaskRunnerServiceOptions for configuration management. - Updated appsettings.json to include TaskRunner configuration. - Enhanced PackRunWorkerService to handle execution graphs and state management. - Added support for parallel execution and conditional steps in the worker service. - Updated documentation to reflect new features and changes in execution flow.
This commit is contained in:
@@ -8,7 +8,8 @@ using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -1535,11 +1536,11 @@ public sealed class CommandHandlersTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandlePolicySimulateAsync_MapsErrorCodes()
|
||||
{
|
||||
var originalExit = Environment.ExitCode;
|
||||
var originalOut = Console.Out;
|
||||
|
||||
public async Task HandlePolicySimulateAsync_MapsErrorCodes()
|
||||
{
|
||||
var originalExit = Environment.ExitCode;
|
||||
var originalOut = Console.Out;
|
||||
|
||||
var backend = new StubBackendClient(new JobTriggerResult(true, "ok", null, null))
|
||||
{
|
||||
SimulationException = new PolicyApiException("Missing inputs", HttpStatusCode.BadRequest, "ERR_POL_003")
|
||||
@@ -1566,18 +1567,185 @@ public sealed class CommandHandlersTests
|
||||
cancellationToken: CancellationToken.None);
|
||||
|
||||
Assert.Equal(21, Environment.ExitCode);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.SetOut(originalOut);
|
||||
Environment.ExitCode = originalExit;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandlePolicyActivateAsync_DisplaysInteractiveSummary()
|
||||
{
|
||||
var originalExit = Environment.ExitCode;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.SetOut(originalOut);
|
||||
Environment.ExitCode = originalExit;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleTaskRunnerSimulateAsync_WritesInteractiveSummary()
|
||||
{
|
||||
var originalExit = Environment.ExitCode;
|
||||
var originalConsole = AnsiConsole.Console;
|
||||
|
||||
var console = new TestConsole();
|
||||
console.Width(120);
|
||||
console.Interactive();
|
||||
console.EmitAnsiSequences();
|
||||
AnsiConsole.Console = console;
|
||||
|
||||
const string manifest = """
|
||||
apiVersion: stellaops.io/pack.v1
|
||||
kind: TaskPack
|
||||
metadata:
|
||||
name: sample-pack
|
||||
spec:
|
||||
steps:
|
||||
- id: prepare
|
||||
run:
|
||||
uses: builtin:prepare
|
||||
- id: approval
|
||||
gate:
|
||||
approval:
|
||||
id: security-review
|
||||
message: Security approval required.
|
||||
""";
|
||||
|
||||
using var manifestFile = new TempFile("pack.yaml", Encoding.UTF8.GetBytes(manifest));
|
||||
|
||||
var simulationResult = new TaskRunnerSimulationResult(
|
||||
"hash-abc123",
|
||||
new TaskRunnerSimulationFailurePolicy(3, 15, false),
|
||||
new[]
|
||||
{
|
||||
new TaskRunnerSimulationStep(
|
||||
"prepare",
|
||||
"prepare",
|
||||
"Run",
|
||||
true,
|
||||
"succeeded",
|
||||
null,
|
||||
"builtin:prepare",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
Array.Empty<TaskRunnerSimulationStep>()),
|
||||
new TaskRunnerSimulationStep(
|
||||
"approval",
|
||||
"approval",
|
||||
"GateApproval",
|
||||
true,
|
||||
"pending",
|
||||
"requires-approval",
|
||||
null,
|
||||
"security-review",
|
||||
"Security approval required.",
|
||||
null,
|
||||
false,
|
||||
Array.Empty<TaskRunnerSimulationStep>())
|
||||
},
|
||||
new[]
|
||||
{
|
||||
new TaskRunnerSimulationOutput("bundlePath", "file", false, "artifacts/report.json", null)
|
||||
},
|
||||
true);
|
||||
|
||||
var backend = new StubBackendClient(new JobTriggerResult(true, "ok", null, null))
|
||||
{
|
||||
TaskRunnerSimulationResult = simulationResult
|
||||
};
|
||||
var provider = BuildServiceProvider(backend);
|
||||
|
||||
try
|
||||
{
|
||||
await CommandHandlers.HandleTaskRunnerSimulateAsync(
|
||||
provider,
|
||||
manifestFile.Path,
|
||||
inputsPath: null,
|
||||
format: null,
|
||||
outputPath: null,
|
||||
verbose: false,
|
||||
cancellationToken: CancellationToken.None);
|
||||
|
||||
Assert.Equal(0, Environment.ExitCode);
|
||||
Assert.NotNull(backend.LastTaskRunnerSimulationRequest);
|
||||
Assert.Contains("approval", console.Output, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains("Plan Hash", console.Output, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
finally
|
||||
{
|
||||
AnsiConsole.Console = originalConsole;
|
||||
Environment.ExitCode = originalExit;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleTaskRunnerSimulateAsync_WritesJsonOutput()
|
||||
{
|
||||
var originalExit = Environment.ExitCode;
|
||||
var originalOut = Console.Out;
|
||||
|
||||
const string manifest = """
|
||||
apiVersion: stellaops.io/pack.v1
|
||||
kind: TaskPack
|
||||
metadata:
|
||||
name: sample-pack
|
||||
spec:
|
||||
steps:
|
||||
- id: prepare
|
||||
run:
|
||||
uses: builtin:prepare
|
||||
""";
|
||||
|
||||
using var manifestFile = new TempFile("pack.yaml", Encoding.UTF8.GetBytes(manifest));
|
||||
using var inputsFile = new TempFile("inputs.json", Encoding.UTF8.GetBytes("{\"dryRun\":false}"));
|
||||
using var outputDirectory = new TempDirectory();
|
||||
var outputPath = Path.Combine(outputDirectory.Path, "simulation.json");
|
||||
|
||||
var simulationResult = new TaskRunnerSimulationResult(
|
||||
"hash-xyz789",
|
||||
new TaskRunnerSimulationFailurePolicy(2, 10, true),
|
||||
Array.Empty<TaskRunnerSimulationStep>(),
|
||||
Array.Empty<TaskRunnerSimulationOutput>(),
|
||||
false);
|
||||
|
||||
var backend = new StubBackendClient(new JobTriggerResult(true, "ok", null, null))
|
||||
{
|
||||
TaskRunnerSimulationResult = simulationResult
|
||||
};
|
||||
var provider = BuildServiceProvider(backend);
|
||||
|
||||
using var writer = new StringWriter();
|
||||
Console.SetOut(writer);
|
||||
|
||||
try
|
||||
{
|
||||
await CommandHandlers.HandleTaskRunnerSimulateAsync(
|
||||
provider,
|
||||
manifestFile.Path,
|
||||
inputsFile.Path,
|
||||
format: "json",
|
||||
outputPath: outputPath,
|
||||
verbose: false,
|
||||
cancellationToken: CancellationToken.None);
|
||||
|
||||
Assert.Equal(0, Environment.ExitCode);
|
||||
Assert.NotNull(backend.LastTaskRunnerSimulationRequest);
|
||||
|
||||
var consoleOutput = writer.ToString();
|
||||
Assert.Contains("\"planHash\":\"hash-xyz789\"", consoleOutput, StringComparison.Ordinal);
|
||||
|
||||
var fileOutput = await File.ReadAllTextAsync(outputPath);
|
||||
Assert.Contains("\"planHash\":\"hash-xyz789\"", fileOutput, StringComparison.Ordinal);
|
||||
|
||||
Assert.True(backend.LastTaskRunnerSimulationRequest!.Inputs!.TryGetPropertyValue("dryRun", out var dryRunNode));
|
||||
Assert.False(dryRunNode!.GetValue<bool>());
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.SetOut(originalOut);
|
||||
Environment.ExitCode = originalExit;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandlePolicyActivateAsync_DisplaysInteractiveSummary()
|
||||
{
|
||||
var originalExit = Environment.ExitCode;
|
||||
var originalConsole = AnsiConsole.Console;
|
||||
|
||||
var console = new TestConsole();
|
||||
@@ -2397,7 +2565,15 @@ public sealed class CommandHandlersTests
|
||||
new ReadOnlyCollection<PolicySimulationRuleDelta>(Array.Empty<PolicySimulationRuleDelta>())),
|
||||
null);
|
||||
public PolicyApiException? SimulationException { get; set; }
|
||||
public (string PolicyId, PolicySimulationInput Input)? LastPolicySimulation { get; private set; }
|
||||
public (string PolicyId, PolicySimulationInput Input)? LastPolicySimulation { get; private set; }
|
||||
public TaskRunnerSimulationRequest? LastTaskRunnerSimulationRequest { get; private set; }
|
||||
public TaskRunnerSimulationResult TaskRunnerSimulationResult { get; set; } = new(
|
||||
string.Empty,
|
||||
new TaskRunnerSimulationFailurePolicy(1, 0, false),
|
||||
Array.Empty<TaskRunnerSimulationStep>(),
|
||||
Array.Empty<TaskRunnerSimulationOutput>(),
|
||||
false);
|
||||
public Exception? TaskRunnerSimulationException { get; set; }
|
||||
public PolicyActivationResult ActivationResult { get; set; } = new PolicyActivationResult(
|
||||
"activated",
|
||||
new PolicyActivationRevision(
|
||||
@@ -2486,17 +2662,28 @@ public sealed class CommandHandlersTests
|
||||
public Task<RuntimePolicyEvaluationResult> EvaluateRuntimePolicyAsync(RuntimePolicyEvaluationRequest request, CancellationToken cancellationToken)
|
||||
=> Task.FromResult(RuntimePolicyResult);
|
||||
|
||||
public Task<PolicySimulationResult> SimulatePolicyAsync(string policyId, PolicySimulationInput input, CancellationToken cancellationToken)
|
||||
{
|
||||
LastPolicySimulation = (policyId, input);
|
||||
if (SimulationException is not null)
|
||||
{
|
||||
throw SimulationException;
|
||||
}
|
||||
|
||||
return Task.FromResult(SimulationResult);
|
||||
}
|
||||
|
||||
public Task<PolicySimulationResult> SimulatePolicyAsync(string policyId, PolicySimulationInput input, CancellationToken cancellationToken)
|
||||
{
|
||||
LastPolicySimulation = (policyId, input);
|
||||
if (SimulationException is not null)
|
||||
{
|
||||
throw SimulationException;
|
||||
}
|
||||
|
||||
return Task.FromResult(SimulationResult);
|
||||
}
|
||||
|
||||
public Task<TaskRunnerSimulationResult> SimulateTaskRunnerAsync(TaskRunnerSimulationRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
LastTaskRunnerSimulationRequest = request;
|
||||
if (TaskRunnerSimulationException is not null)
|
||||
{
|
||||
throw TaskRunnerSimulationException;
|
||||
}
|
||||
|
||||
return Task.FromResult(TaskRunnerSimulationResult);
|
||||
}
|
||||
|
||||
public Task<PolicyActivationResult> ActivatePolicyRevisionAsync(string policyId, int version, PolicyActivationRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
LastPolicyActivation = (policyId, version, request);
|
||||
|
||||
Reference in New Issue
Block a user