feat: Implement Policy Engine Evaluation Service and Cache with unit tests
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Temp commit to debug
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
|
||||
namespace StellaOps.TaskRunner.Core.Execution;
|
||||
|
||||
public interface IPackRunStepExecutor
|
||||
{
|
||||
Task<PackRunStepExecutionResult> ExecuteAsync(
|
||||
PackRunExecutionStep step,
|
||||
IReadOnlyDictionary<string, TaskPackPlanParameterValue> parameters,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public sealed record PackRunStepExecutionResult(bool Succeeded, string? Error = null);
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
|
||||
namespace StellaOps.TaskRunner.Core.Execution;
|
||||
|
||||
public interface IPackRunStepExecutor
|
||||
{
|
||||
Task<PackRunStepExecutionResult> ExecuteAsync(
|
||||
PackRunExecutionStep step,
|
||||
IReadOnlyDictionary<string, TaskPackPlanParameterValue> parameters,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public sealed record PackRunStepExecutionResult(bool Succeeded, string? Error = null);
|
||||
|
||||
@@ -1,86 +1,86 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
|
||||
namespace StellaOps.TaskRunner.Core.Execution;
|
||||
|
||||
public sealed class PackRunExecutionGraph
|
||||
{
|
||||
public static readonly TaskPackPlanFailurePolicy DefaultFailurePolicy = new(1, 0, ContinueOnError: false);
|
||||
|
||||
public PackRunExecutionGraph(IReadOnlyList<PackRunExecutionStep> steps, TaskPackPlanFailurePolicy? failurePolicy)
|
||||
{
|
||||
Steps = steps ?? throw new ArgumentNullException(nameof(steps));
|
||||
FailurePolicy = failurePolicy ?? DefaultFailurePolicy;
|
||||
}
|
||||
|
||||
public IReadOnlyList<PackRunExecutionStep> Steps { get; }
|
||||
|
||||
public TaskPackPlanFailurePolicy FailurePolicy { get; }
|
||||
}
|
||||
|
||||
public enum PackRunStepKind
|
||||
{
|
||||
Unknown = 0,
|
||||
Run,
|
||||
GateApproval,
|
||||
GatePolicy,
|
||||
Parallel,
|
||||
Map
|
||||
}
|
||||
|
||||
public sealed class PackRunExecutionStep
|
||||
{
|
||||
public PackRunExecutionStep(
|
||||
string id,
|
||||
string templateId,
|
||||
PackRunStepKind kind,
|
||||
bool enabled,
|
||||
string? uses,
|
||||
IReadOnlyDictionary<string, TaskPackPlanParameterValue> parameters,
|
||||
string? approvalId,
|
||||
string? gateMessage,
|
||||
int? maxParallel,
|
||||
bool continueOnError,
|
||||
IReadOnlyList<PackRunExecutionStep> children)
|
||||
{
|
||||
Id = string.IsNullOrWhiteSpace(id) ? throw new ArgumentException("Value cannot be null or whitespace.", nameof(id)) : id;
|
||||
TemplateId = string.IsNullOrWhiteSpace(templateId) ? throw new ArgumentException("Value cannot be null or whitespace.", nameof(templateId)) : templateId;
|
||||
Kind = kind;
|
||||
Enabled = enabled;
|
||||
Uses = uses;
|
||||
Parameters = parameters ?? throw new ArgumentNullException(nameof(parameters));
|
||||
ApprovalId = approvalId;
|
||||
GateMessage = gateMessage;
|
||||
MaxParallel = maxParallel;
|
||||
ContinueOnError = continueOnError;
|
||||
Children = children ?? throw new ArgumentNullException(nameof(children));
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public string TemplateId { get; }
|
||||
|
||||
public PackRunStepKind Kind { get; }
|
||||
|
||||
public bool Enabled { get; }
|
||||
|
||||
public string? Uses { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, TaskPackPlanParameterValue> Parameters { get; }
|
||||
|
||||
public string? ApprovalId { get; }
|
||||
|
||||
public string? GateMessage { get; }
|
||||
|
||||
public int? MaxParallel { get; }
|
||||
|
||||
public bool ContinueOnError { get; }
|
||||
|
||||
public IReadOnlyList<PackRunExecutionStep> Children { get; }
|
||||
|
||||
public static IReadOnlyDictionary<string, TaskPackPlanParameterValue> EmptyParameters { get; } =
|
||||
new ReadOnlyDictionary<string, TaskPackPlanParameterValue>(new Dictionary<string, TaskPackPlanParameterValue>(StringComparer.Ordinal));
|
||||
|
||||
public static IReadOnlyList<PackRunExecutionStep> EmptyChildren { get; } =
|
||||
Array.Empty<PackRunExecutionStep>();
|
||||
}
|
||||
using System.Collections.ObjectModel;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
|
||||
namespace StellaOps.TaskRunner.Core.Execution;
|
||||
|
||||
public sealed class PackRunExecutionGraph
|
||||
{
|
||||
public static readonly TaskPackPlanFailurePolicy DefaultFailurePolicy = new(1, 0, ContinueOnError: false);
|
||||
|
||||
public PackRunExecutionGraph(IReadOnlyList<PackRunExecutionStep> steps, TaskPackPlanFailurePolicy? failurePolicy)
|
||||
{
|
||||
Steps = steps ?? throw new ArgumentNullException(nameof(steps));
|
||||
FailurePolicy = failurePolicy ?? DefaultFailurePolicy;
|
||||
}
|
||||
|
||||
public IReadOnlyList<PackRunExecutionStep> Steps { get; }
|
||||
|
||||
public TaskPackPlanFailurePolicy FailurePolicy { get; }
|
||||
}
|
||||
|
||||
public enum PackRunStepKind
|
||||
{
|
||||
Unknown = 0,
|
||||
Run,
|
||||
GateApproval,
|
||||
GatePolicy,
|
||||
Parallel,
|
||||
Map
|
||||
}
|
||||
|
||||
public sealed class PackRunExecutionStep
|
||||
{
|
||||
public PackRunExecutionStep(
|
||||
string id,
|
||||
string templateId,
|
||||
PackRunStepKind kind,
|
||||
bool enabled,
|
||||
string? uses,
|
||||
IReadOnlyDictionary<string, TaskPackPlanParameterValue> parameters,
|
||||
string? approvalId,
|
||||
string? gateMessage,
|
||||
int? maxParallel,
|
||||
bool continueOnError,
|
||||
IReadOnlyList<PackRunExecutionStep> children)
|
||||
{
|
||||
Id = string.IsNullOrWhiteSpace(id) ? throw new ArgumentException("Value cannot be null or whitespace.", nameof(id)) : id;
|
||||
TemplateId = string.IsNullOrWhiteSpace(templateId) ? throw new ArgumentException("Value cannot be null or whitespace.", nameof(templateId)) : templateId;
|
||||
Kind = kind;
|
||||
Enabled = enabled;
|
||||
Uses = uses;
|
||||
Parameters = parameters ?? throw new ArgumentNullException(nameof(parameters));
|
||||
ApprovalId = approvalId;
|
||||
GateMessage = gateMessage;
|
||||
MaxParallel = maxParallel;
|
||||
ContinueOnError = continueOnError;
|
||||
Children = children ?? throw new ArgumentNullException(nameof(children));
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public string TemplateId { get; }
|
||||
|
||||
public PackRunStepKind Kind { get; }
|
||||
|
||||
public bool Enabled { get; }
|
||||
|
||||
public string? Uses { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, TaskPackPlanParameterValue> Parameters { get; }
|
||||
|
||||
public string? ApprovalId { get; }
|
||||
|
||||
public string? GateMessage { get; }
|
||||
|
||||
public int? MaxParallel { get; }
|
||||
|
||||
public bool ContinueOnError { get; }
|
||||
|
||||
public IReadOnlyList<PackRunExecutionStep> Children { get; }
|
||||
|
||||
public static IReadOnlyDictionary<string, TaskPackPlanParameterValue> EmptyParameters { get; } =
|
||||
new ReadOnlyDictionary<string, TaskPackPlanParameterValue>(new Dictionary<string, TaskPackPlanParameterValue>(StringComparer.Ordinal));
|
||||
|
||||
public static IReadOnlyList<PackRunExecutionStep> EmptyChildren { get; } =
|
||||
Array.Empty<PackRunExecutionStep>();
|
||||
}
|
||||
|
||||
@@ -1,77 +1,77 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text.Json.Nodes;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
|
||||
namespace StellaOps.TaskRunner.Core.Execution;
|
||||
|
||||
public sealed class PackRunExecutionGraphBuilder
|
||||
{
|
||||
public PackRunExecutionGraph Build(TaskPackPlan plan)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(plan);
|
||||
|
||||
var steps = plan.Steps.Select(ConvertStep).ToList();
|
||||
var failurePolicy = plan.FailurePolicy;
|
||||
return new PackRunExecutionGraph(steps, failurePolicy);
|
||||
}
|
||||
|
||||
private static PackRunExecutionStep ConvertStep(TaskPackPlanStep step)
|
||||
{
|
||||
var kind = DetermineKind(step.Type);
|
||||
var parameters = step.Parameters is null
|
||||
? PackRunExecutionStep.EmptyParameters
|
||||
: new ReadOnlyDictionary<string, TaskPackPlanParameterValue>(
|
||||
new Dictionary<string, TaskPackPlanParameterValue>(step.Parameters, StringComparer.Ordinal));
|
||||
|
||||
var children = step.Children is null
|
||||
? PackRunExecutionStep.EmptyChildren
|
||||
: step.Children.Select(ConvertStep).ToList();
|
||||
|
||||
var maxParallel = TryGetInt(parameters, "maxParallel");
|
||||
var continueOnError = TryGetBool(parameters, "continueOnError");
|
||||
|
||||
return new PackRunExecutionStep(
|
||||
step.Id,
|
||||
step.TemplateId,
|
||||
kind,
|
||||
step.Enabled,
|
||||
step.Uses,
|
||||
parameters,
|
||||
step.ApprovalId,
|
||||
step.GateMessage,
|
||||
maxParallel,
|
||||
continueOnError,
|
||||
children);
|
||||
}
|
||||
|
||||
private static PackRunStepKind DetermineKind(string? type)
|
||||
=> type switch
|
||||
{
|
||||
"run" => PackRunStepKind.Run,
|
||||
"gate.approval" => PackRunStepKind.GateApproval,
|
||||
"gate.policy" => PackRunStepKind.GatePolicy,
|
||||
"parallel" => PackRunStepKind.Parallel,
|
||||
"map" => PackRunStepKind.Map,
|
||||
_ => PackRunStepKind.Unknown
|
||||
};
|
||||
|
||||
private static int? TryGetInt(IReadOnlyDictionary<string, TaskPackPlanParameterValue> parameters, string key)
|
||||
{
|
||||
if (!parameters.TryGetValue(key, out var value) || value.Value is not JsonValue jsonValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return jsonValue.TryGetValue<int>(out var result) ? result : null;
|
||||
}
|
||||
|
||||
private static bool TryGetBool(IReadOnlyDictionary<string, TaskPackPlanParameterValue> parameters, string key)
|
||||
{
|
||||
if (!parameters.TryGetValue(key, out var value) || value.Value is not JsonValue jsonValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return jsonValue.TryGetValue<bool>(out var result) && result;
|
||||
}
|
||||
}
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text.Json.Nodes;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
|
||||
namespace StellaOps.TaskRunner.Core.Execution;
|
||||
|
||||
public sealed class PackRunExecutionGraphBuilder
|
||||
{
|
||||
public PackRunExecutionGraph Build(TaskPackPlan plan)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(plan);
|
||||
|
||||
var steps = plan.Steps.Select(ConvertStep).ToList();
|
||||
var failurePolicy = plan.FailurePolicy;
|
||||
return new PackRunExecutionGraph(steps, failurePolicy);
|
||||
}
|
||||
|
||||
private static PackRunExecutionStep ConvertStep(TaskPackPlanStep step)
|
||||
{
|
||||
var kind = DetermineKind(step.Type);
|
||||
var parameters = step.Parameters is null
|
||||
? PackRunExecutionStep.EmptyParameters
|
||||
: new ReadOnlyDictionary<string, TaskPackPlanParameterValue>(
|
||||
new Dictionary<string, TaskPackPlanParameterValue>(step.Parameters, StringComparer.Ordinal));
|
||||
|
||||
var children = step.Children is null
|
||||
? PackRunExecutionStep.EmptyChildren
|
||||
: step.Children.Select(ConvertStep).ToList();
|
||||
|
||||
var maxParallel = TryGetInt(parameters, "maxParallel");
|
||||
var continueOnError = TryGetBool(parameters, "continueOnError");
|
||||
|
||||
return new PackRunExecutionStep(
|
||||
step.Id,
|
||||
step.TemplateId,
|
||||
kind,
|
||||
step.Enabled,
|
||||
step.Uses,
|
||||
parameters,
|
||||
step.ApprovalId,
|
||||
step.GateMessage,
|
||||
maxParallel,
|
||||
continueOnError,
|
||||
children);
|
||||
}
|
||||
|
||||
private static PackRunStepKind DetermineKind(string? type)
|
||||
=> type switch
|
||||
{
|
||||
"run" => PackRunStepKind.Run,
|
||||
"gate.approval" => PackRunStepKind.GateApproval,
|
||||
"gate.policy" => PackRunStepKind.GatePolicy,
|
||||
"parallel" => PackRunStepKind.Parallel,
|
||||
"map" => PackRunStepKind.Map,
|
||||
_ => PackRunStepKind.Unknown
|
||||
};
|
||||
|
||||
private static int? TryGetInt(IReadOnlyDictionary<string, TaskPackPlanParameterValue> parameters, string key)
|
||||
{
|
||||
if (!parameters.TryGetValue(key, out var value) || value.Value is not JsonValue jsonValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return jsonValue.TryGetValue<int>(out var result) ? result : null;
|
||||
}
|
||||
|
||||
private static bool TryGetBool(IReadOnlyDictionary<string, TaskPackPlanParameterValue> parameters, string key)
|
||||
{
|
||||
if (!parameters.TryGetValue(key, out var value) || value.Value is not JsonValue jsonValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return jsonValue.TryGetValue<bool>(out var result) && result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,159 +1,159 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace StellaOps.TaskRunner.Core.Execution;
|
||||
|
||||
public static class PackRunGateStateUpdater
|
||||
{
|
||||
public static PackRunGateStateUpdateResult Apply(
|
||||
PackRunState state,
|
||||
PackRunExecutionGraph graph,
|
||||
PackRunApprovalCoordinator coordinator,
|
||||
DateTimeOffset timestamp)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(state);
|
||||
ArgumentNullException.ThrowIfNull(graph);
|
||||
ArgumentNullException.ThrowIfNull(coordinator);
|
||||
|
||||
var approvals = coordinator.GetApprovals()
|
||||
.SelectMany(approval => approval.StepIds.Select(stepId => (stepId, approval)))
|
||||
.GroupBy(tuple => tuple.stepId, StringComparer.Ordinal)
|
||||
.ToDictionary(
|
||||
group => group.Key,
|
||||
group => group.First().approval,
|
||||
StringComparer.Ordinal);
|
||||
|
||||
var mutable = new Dictionary<string, PackRunStepStateRecord>(state.Steps, StringComparer.Ordinal);
|
||||
var changed = false;
|
||||
var hasBlockingFailure = false;
|
||||
|
||||
foreach (var step in EnumerateSteps(graph.Steps))
|
||||
{
|
||||
if (!mutable.TryGetValue(step.Id, out var record))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (step.Kind)
|
||||
{
|
||||
case PackRunStepKind.GateApproval:
|
||||
if (!approvals.TryGetValue(step.Id, out var approvalState))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (approvalState.Status)
|
||||
{
|
||||
case PackRunApprovalStatus.Pending:
|
||||
break;
|
||||
|
||||
case PackRunApprovalStatus.Approved:
|
||||
if (record.Status != PackRunStepExecutionStatus.Succeeded || record.StatusReason is not null)
|
||||
{
|
||||
mutable[step.Id] = record with
|
||||
{
|
||||
Status = PackRunStepExecutionStatus.Succeeded,
|
||||
StatusReason = null,
|
||||
LastTransitionAt = timestamp,
|
||||
NextAttemptAt = null
|
||||
};
|
||||
changed = true;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case PackRunApprovalStatus.Rejected:
|
||||
case PackRunApprovalStatus.Expired:
|
||||
var failureReason = BuildFailureReason(approvalState);
|
||||
if (record.Status != PackRunStepExecutionStatus.Failed ||
|
||||
!string.Equals(record.StatusReason, failureReason, StringComparison.Ordinal))
|
||||
{
|
||||
mutable[step.Id] = record with
|
||||
{
|
||||
Status = PackRunStepExecutionStatus.Failed,
|
||||
StatusReason = failureReason,
|
||||
LastTransitionAt = timestamp,
|
||||
NextAttemptAt = null
|
||||
};
|
||||
changed = true;
|
||||
}
|
||||
|
||||
hasBlockingFailure = true;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case PackRunStepKind.GatePolicy:
|
||||
if (record.Status == PackRunStepExecutionStatus.Pending &&
|
||||
string.Equals(record.StatusReason, "requires-policy", StringComparison.Ordinal))
|
||||
{
|
||||
mutable[step.Id] = record with
|
||||
{
|
||||
Status = PackRunStepExecutionStatus.Succeeded,
|
||||
StatusReason = null,
|
||||
LastTransitionAt = timestamp,
|
||||
NextAttemptAt = null
|
||||
};
|
||||
changed = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!changed)
|
||||
{
|
||||
return new PackRunGateStateUpdateResult(state, hasBlockingFailure);
|
||||
}
|
||||
|
||||
var updatedState = state with
|
||||
{
|
||||
UpdatedAt = timestamp,
|
||||
Steps = new ReadOnlyDictionary<string, PackRunStepStateRecord>(mutable)
|
||||
};
|
||||
|
||||
return new PackRunGateStateUpdateResult(updatedState, hasBlockingFailure);
|
||||
}
|
||||
|
||||
private static IEnumerable<PackRunExecutionStep> EnumerateSteps(IReadOnlyList<PackRunExecutionStep> steps)
|
||||
{
|
||||
if (steps.Count == 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var step in steps)
|
||||
{
|
||||
yield return step;
|
||||
|
||||
if (step.Children.Count > 0)
|
||||
{
|
||||
foreach (var child in EnumerateSteps(step.Children))
|
||||
{
|
||||
yield return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildFailureReason(PackRunApprovalState state)
|
||||
{
|
||||
var baseReason = state.Status switch
|
||||
{
|
||||
PackRunApprovalStatus.Rejected => "approval-rejected",
|
||||
PackRunApprovalStatus.Expired => "approval-expired",
|
||||
_ => "approval-invalid"
|
||||
};
|
||||
|
||||
if (string.IsNullOrWhiteSpace(state.Summary))
|
||||
{
|
||||
return baseReason;
|
||||
}
|
||||
|
||||
var summary = state.Summary.Trim();
|
||||
return $"{baseReason}:{summary}";
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct PackRunGateStateUpdateResult(PackRunState State, bool HasBlockingFailure);
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace StellaOps.TaskRunner.Core.Execution;
|
||||
|
||||
public static class PackRunGateStateUpdater
|
||||
{
|
||||
public static PackRunGateStateUpdateResult Apply(
|
||||
PackRunState state,
|
||||
PackRunExecutionGraph graph,
|
||||
PackRunApprovalCoordinator coordinator,
|
||||
DateTimeOffset timestamp)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(state);
|
||||
ArgumentNullException.ThrowIfNull(graph);
|
||||
ArgumentNullException.ThrowIfNull(coordinator);
|
||||
|
||||
var approvals = coordinator.GetApprovals()
|
||||
.SelectMany(approval => approval.StepIds.Select(stepId => (stepId, approval)))
|
||||
.GroupBy(tuple => tuple.stepId, StringComparer.Ordinal)
|
||||
.ToDictionary(
|
||||
group => group.Key,
|
||||
group => group.First().approval,
|
||||
StringComparer.Ordinal);
|
||||
|
||||
var mutable = new Dictionary<string, PackRunStepStateRecord>(state.Steps, StringComparer.Ordinal);
|
||||
var changed = false;
|
||||
var hasBlockingFailure = false;
|
||||
|
||||
foreach (var step in EnumerateSteps(graph.Steps))
|
||||
{
|
||||
if (!mutable.TryGetValue(step.Id, out var record))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (step.Kind)
|
||||
{
|
||||
case PackRunStepKind.GateApproval:
|
||||
if (!approvals.TryGetValue(step.Id, out var approvalState))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (approvalState.Status)
|
||||
{
|
||||
case PackRunApprovalStatus.Pending:
|
||||
break;
|
||||
|
||||
case PackRunApprovalStatus.Approved:
|
||||
if (record.Status != PackRunStepExecutionStatus.Succeeded || record.StatusReason is not null)
|
||||
{
|
||||
mutable[step.Id] = record with
|
||||
{
|
||||
Status = PackRunStepExecutionStatus.Succeeded,
|
||||
StatusReason = null,
|
||||
LastTransitionAt = timestamp,
|
||||
NextAttemptAt = null
|
||||
};
|
||||
changed = true;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case PackRunApprovalStatus.Rejected:
|
||||
case PackRunApprovalStatus.Expired:
|
||||
var failureReason = BuildFailureReason(approvalState);
|
||||
if (record.Status != PackRunStepExecutionStatus.Failed ||
|
||||
!string.Equals(record.StatusReason, failureReason, StringComparison.Ordinal))
|
||||
{
|
||||
mutable[step.Id] = record with
|
||||
{
|
||||
Status = PackRunStepExecutionStatus.Failed,
|
||||
StatusReason = failureReason,
|
||||
LastTransitionAt = timestamp,
|
||||
NextAttemptAt = null
|
||||
};
|
||||
changed = true;
|
||||
}
|
||||
|
||||
hasBlockingFailure = true;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case PackRunStepKind.GatePolicy:
|
||||
if (record.Status == PackRunStepExecutionStatus.Pending &&
|
||||
string.Equals(record.StatusReason, "requires-policy", StringComparison.Ordinal))
|
||||
{
|
||||
mutable[step.Id] = record with
|
||||
{
|
||||
Status = PackRunStepExecutionStatus.Succeeded,
|
||||
StatusReason = null,
|
||||
LastTransitionAt = timestamp,
|
||||
NextAttemptAt = null
|
||||
};
|
||||
changed = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!changed)
|
||||
{
|
||||
return new PackRunGateStateUpdateResult(state, hasBlockingFailure);
|
||||
}
|
||||
|
||||
var updatedState = state with
|
||||
{
|
||||
UpdatedAt = timestamp,
|
||||
Steps = new ReadOnlyDictionary<string, PackRunStepStateRecord>(mutable)
|
||||
};
|
||||
|
||||
return new PackRunGateStateUpdateResult(updatedState, hasBlockingFailure);
|
||||
}
|
||||
|
||||
private static IEnumerable<PackRunExecutionStep> EnumerateSteps(IReadOnlyList<PackRunExecutionStep> steps)
|
||||
{
|
||||
if (steps.Count == 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var step in steps)
|
||||
{
|
||||
yield return step;
|
||||
|
||||
if (step.Children.Count > 0)
|
||||
{
|
||||
foreach (var child in EnumerateSteps(step.Children))
|
||||
{
|
||||
yield return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildFailureReason(PackRunApprovalState state)
|
||||
{
|
||||
var baseReason = state.Status switch
|
||||
{
|
||||
PackRunApprovalStatus.Rejected => "approval-rejected",
|
||||
PackRunApprovalStatus.Expired => "approval-expired",
|
||||
_ => "approval-invalid"
|
||||
};
|
||||
|
||||
if (string.IsNullOrWhiteSpace(state.Summary))
|
||||
{
|
||||
return baseReason;
|
||||
}
|
||||
|
||||
var summary = state.Summary.Trim();
|
||||
return $"{baseReason}:{summary}";
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct PackRunGateStateUpdateResult(PackRunState State, bool HasBlockingFailure);
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
|
||||
namespace StellaOps.TaskRunner.Core.Execution;
|
||||
|
||||
public sealed record PackRunState(
|
||||
string RunId,
|
||||
string PlanHash,
|
||||
TaskPackPlanFailurePolicy FailurePolicy,
|
||||
DateTimeOffset CreatedAt,
|
||||
DateTimeOffset UpdatedAt,
|
||||
IReadOnlyDictionary<string, PackRunStepStateRecord> Steps)
|
||||
{
|
||||
public static PackRunState Create(
|
||||
string runId,
|
||||
string planHash,
|
||||
TaskPackPlanFailurePolicy failurePolicy,
|
||||
IReadOnlyDictionary<string, PackRunStepStateRecord> steps,
|
||||
DateTimeOffset timestamp)
|
||||
=> new(
|
||||
runId,
|
||||
planHash,
|
||||
failurePolicy,
|
||||
timestamp,
|
||||
timestamp,
|
||||
new ReadOnlyDictionary<string, PackRunStepStateRecord>(new Dictionary<string, PackRunStepStateRecord>(steps, StringComparer.Ordinal)));
|
||||
}
|
||||
|
||||
public sealed record PackRunStepStateRecord(
|
||||
string StepId,
|
||||
PackRunStepKind Kind,
|
||||
bool Enabled,
|
||||
bool ContinueOnError,
|
||||
int? MaxParallel,
|
||||
string? ApprovalId,
|
||||
string? GateMessage,
|
||||
PackRunStepExecutionStatus Status,
|
||||
int Attempts,
|
||||
DateTimeOffset? LastTransitionAt,
|
||||
DateTimeOffset? NextAttemptAt,
|
||||
string? StatusReason);
|
||||
|
||||
public interface IPackRunStateStore
|
||||
{
|
||||
Task<PackRunState?> GetAsync(string runId, CancellationToken cancellationToken);
|
||||
|
||||
Task SaveAsync(PackRunState state, CancellationToken cancellationToken);
|
||||
|
||||
Task<IReadOnlyList<PackRunState>> ListAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
using System.Collections.ObjectModel;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
|
||||
namespace StellaOps.TaskRunner.Core.Execution;
|
||||
|
||||
public sealed record PackRunState(
|
||||
string RunId,
|
||||
string PlanHash,
|
||||
TaskPackPlanFailurePolicy FailurePolicy,
|
||||
DateTimeOffset CreatedAt,
|
||||
DateTimeOffset UpdatedAt,
|
||||
IReadOnlyDictionary<string, PackRunStepStateRecord> Steps)
|
||||
{
|
||||
public static PackRunState Create(
|
||||
string runId,
|
||||
string planHash,
|
||||
TaskPackPlanFailurePolicy failurePolicy,
|
||||
IReadOnlyDictionary<string, PackRunStepStateRecord> steps,
|
||||
DateTimeOffset timestamp)
|
||||
=> new(
|
||||
runId,
|
||||
planHash,
|
||||
failurePolicy,
|
||||
timestamp,
|
||||
timestamp,
|
||||
new ReadOnlyDictionary<string, PackRunStepStateRecord>(new Dictionary<string, PackRunStepStateRecord>(steps, StringComparer.Ordinal)));
|
||||
}
|
||||
|
||||
public sealed record PackRunStepStateRecord(
|
||||
string StepId,
|
||||
PackRunStepKind Kind,
|
||||
bool Enabled,
|
||||
bool ContinueOnError,
|
||||
int? MaxParallel,
|
||||
string? ApprovalId,
|
||||
string? GateMessage,
|
||||
PackRunStepExecutionStatus Status,
|
||||
int Attempts,
|
||||
DateTimeOffset? LastTransitionAt,
|
||||
DateTimeOffset? NextAttemptAt,
|
||||
string? StatusReason);
|
||||
|
||||
public interface IPackRunStateStore
|
||||
{
|
||||
Task<PackRunState?> GetAsync(string runId, CancellationToken cancellationToken);
|
||||
|
||||
Task SaveAsync(PackRunState state, CancellationToken cancellationToken);
|
||||
|
||||
Task<IReadOnlyList<PackRunState>> ListAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -1,121 +1,121 @@
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
|
||||
namespace StellaOps.TaskRunner.Core.Execution;
|
||||
|
||||
public static class PackRunStepStateMachine
|
||||
{
|
||||
public static PackRunStepState Create(DateTimeOffset? createdAt = null)
|
||||
=> new(PackRunStepExecutionStatus.Pending, Attempts: 0, createdAt, NextAttemptAt: null);
|
||||
|
||||
public static PackRunStepState Start(PackRunStepState state, DateTimeOffset startedAt)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(state);
|
||||
if (state.Status is not PackRunStepExecutionStatus.Pending)
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot start step from status {state.Status}.");
|
||||
}
|
||||
|
||||
return state with
|
||||
{
|
||||
Status = PackRunStepExecutionStatus.Running,
|
||||
LastTransitionAt = startedAt,
|
||||
NextAttemptAt = null
|
||||
};
|
||||
}
|
||||
|
||||
public static PackRunStepState CompleteSuccess(PackRunStepState state, DateTimeOffset completedAt)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(state);
|
||||
if (state.Status is not PackRunStepExecutionStatus.Running)
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot complete step from status {state.Status}.");
|
||||
}
|
||||
|
||||
return state with
|
||||
{
|
||||
Status = PackRunStepExecutionStatus.Succeeded,
|
||||
Attempts = state.Attempts + 1,
|
||||
LastTransitionAt = completedAt,
|
||||
NextAttemptAt = null
|
||||
};
|
||||
}
|
||||
|
||||
public static PackRunStepFailureResult RegisterFailure(
|
||||
PackRunStepState state,
|
||||
DateTimeOffset failedAt,
|
||||
TaskPackPlanFailurePolicy failurePolicy)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(state);
|
||||
ArgumentNullException.ThrowIfNull(failurePolicy);
|
||||
|
||||
if (state.Status is not PackRunStepExecutionStatus.Running)
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot register failure from status {state.Status}.");
|
||||
}
|
||||
|
||||
var attempts = state.Attempts + 1;
|
||||
if (attempts < failurePolicy.MaxAttempts)
|
||||
{
|
||||
var backoff = TimeSpan.FromSeconds(Math.Max(0, failurePolicy.BackoffSeconds));
|
||||
var nextAttemptAt = failedAt + backoff;
|
||||
var nextState = state with
|
||||
{
|
||||
Status = PackRunStepExecutionStatus.Pending,
|
||||
Attempts = attempts,
|
||||
LastTransitionAt = failedAt,
|
||||
NextAttemptAt = nextAttemptAt
|
||||
};
|
||||
|
||||
return new PackRunStepFailureResult(nextState, PackRunStepFailureOutcome.Retry);
|
||||
}
|
||||
|
||||
var finalState = state with
|
||||
{
|
||||
Status = PackRunStepExecutionStatus.Failed,
|
||||
Attempts = attempts,
|
||||
LastTransitionAt = failedAt,
|
||||
NextAttemptAt = null
|
||||
};
|
||||
|
||||
return new PackRunStepFailureResult(finalState, PackRunStepFailureOutcome.Abort);
|
||||
}
|
||||
|
||||
public static PackRunStepState Skip(PackRunStepState state, DateTimeOffset skippedAt)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(state);
|
||||
if (state.Status is not PackRunStepExecutionStatus.Pending)
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot skip step from status {state.Status}.");
|
||||
}
|
||||
|
||||
return state with
|
||||
{
|
||||
Status = PackRunStepExecutionStatus.Skipped,
|
||||
LastTransitionAt = skippedAt,
|
||||
NextAttemptAt = null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record PackRunStepState(
|
||||
PackRunStepExecutionStatus Status,
|
||||
int Attempts,
|
||||
DateTimeOffset? LastTransitionAt,
|
||||
DateTimeOffset? NextAttemptAt);
|
||||
|
||||
public enum PackRunStepExecutionStatus
|
||||
{
|
||||
Pending = 0,
|
||||
Running,
|
||||
Succeeded,
|
||||
Failed,
|
||||
Skipped
|
||||
}
|
||||
|
||||
public readonly record struct PackRunStepFailureResult(PackRunStepState State, PackRunStepFailureOutcome Outcome);
|
||||
|
||||
public enum PackRunStepFailureOutcome
|
||||
{
|
||||
Retry = 0,
|
||||
Abort
|
||||
}
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
|
||||
namespace StellaOps.TaskRunner.Core.Execution;
|
||||
|
||||
public static class PackRunStepStateMachine
|
||||
{
|
||||
public static PackRunStepState Create(DateTimeOffset? createdAt = null)
|
||||
=> new(PackRunStepExecutionStatus.Pending, Attempts: 0, createdAt, NextAttemptAt: null);
|
||||
|
||||
public static PackRunStepState Start(PackRunStepState state, DateTimeOffset startedAt)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(state);
|
||||
if (state.Status is not PackRunStepExecutionStatus.Pending)
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot start step from status {state.Status}.");
|
||||
}
|
||||
|
||||
return state with
|
||||
{
|
||||
Status = PackRunStepExecutionStatus.Running,
|
||||
LastTransitionAt = startedAt,
|
||||
NextAttemptAt = null
|
||||
};
|
||||
}
|
||||
|
||||
public static PackRunStepState CompleteSuccess(PackRunStepState state, DateTimeOffset completedAt)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(state);
|
||||
if (state.Status is not PackRunStepExecutionStatus.Running)
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot complete step from status {state.Status}.");
|
||||
}
|
||||
|
||||
return state with
|
||||
{
|
||||
Status = PackRunStepExecutionStatus.Succeeded,
|
||||
Attempts = state.Attempts + 1,
|
||||
LastTransitionAt = completedAt,
|
||||
NextAttemptAt = null
|
||||
};
|
||||
}
|
||||
|
||||
public static PackRunStepFailureResult RegisterFailure(
|
||||
PackRunStepState state,
|
||||
DateTimeOffset failedAt,
|
||||
TaskPackPlanFailurePolicy failurePolicy)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(state);
|
||||
ArgumentNullException.ThrowIfNull(failurePolicy);
|
||||
|
||||
if (state.Status is not PackRunStepExecutionStatus.Running)
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot register failure from status {state.Status}.");
|
||||
}
|
||||
|
||||
var attempts = state.Attempts + 1;
|
||||
if (attempts < failurePolicy.MaxAttempts)
|
||||
{
|
||||
var backoff = TimeSpan.FromSeconds(Math.Max(0, failurePolicy.BackoffSeconds));
|
||||
var nextAttemptAt = failedAt + backoff;
|
||||
var nextState = state with
|
||||
{
|
||||
Status = PackRunStepExecutionStatus.Pending,
|
||||
Attempts = attempts,
|
||||
LastTransitionAt = failedAt,
|
||||
NextAttemptAt = nextAttemptAt
|
||||
};
|
||||
|
||||
return new PackRunStepFailureResult(nextState, PackRunStepFailureOutcome.Retry);
|
||||
}
|
||||
|
||||
var finalState = state with
|
||||
{
|
||||
Status = PackRunStepExecutionStatus.Failed,
|
||||
Attempts = attempts,
|
||||
LastTransitionAt = failedAt,
|
||||
NextAttemptAt = null
|
||||
};
|
||||
|
||||
return new PackRunStepFailureResult(finalState, PackRunStepFailureOutcome.Abort);
|
||||
}
|
||||
|
||||
public static PackRunStepState Skip(PackRunStepState state, DateTimeOffset skippedAt)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(state);
|
||||
if (state.Status is not PackRunStepExecutionStatus.Pending)
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot skip step from status {state.Status}.");
|
||||
}
|
||||
|
||||
return state with
|
||||
{
|
||||
Status = PackRunStepExecutionStatus.Skipped,
|
||||
LastTransitionAt = skippedAt,
|
||||
NextAttemptAt = null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record PackRunStepState(
|
||||
PackRunStepExecutionStatus Status,
|
||||
int Attempts,
|
||||
DateTimeOffset? LastTransitionAt,
|
||||
DateTimeOffset? NextAttemptAt);
|
||||
|
||||
public enum PackRunStepExecutionStatus
|
||||
{
|
||||
Pending = 0,
|
||||
Running,
|
||||
Succeeded,
|
||||
Failed,
|
||||
Skipped
|
||||
}
|
||||
|
||||
public readonly record struct PackRunStepFailureResult(PackRunStepState State, PackRunStepFailureOutcome Outcome);
|
||||
|
||||
public enum PackRunStepFailureOutcome
|
||||
{
|
||||
Retry = 0,
|
||||
Abort
|
||||
}
|
||||
|
||||
@@ -1,78 +1,78 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
|
||||
namespace StellaOps.TaskRunner.Core.Execution.Simulation;
|
||||
|
||||
public sealed class PackRunSimulationEngine
|
||||
{
|
||||
private readonly PackRunExecutionGraphBuilder graphBuilder;
|
||||
|
||||
public PackRunSimulationEngine()
|
||||
{
|
||||
graphBuilder = new PackRunExecutionGraphBuilder();
|
||||
}
|
||||
|
||||
public PackRunSimulationResult Simulate(TaskPackPlan plan)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(plan);
|
||||
|
||||
var graph = graphBuilder.Build(plan);
|
||||
var steps = graph.Steps.Select(ConvertStep).ToList();
|
||||
var outputs = BuildOutputs(plan.Outputs);
|
||||
|
||||
return new PackRunSimulationResult(steps, outputs, graph.FailurePolicy);
|
||||
}
|
||||
|
||||
private static PackRunSimulationNode ConvertStep(PackRunExecutionStep step)
|
||||
{
|
||||
var status = DetermineStatus(step);
|
||||
var children = step.Children.Count == 0
|
||||
? PackRunSimulationNode.Empty
|
||||
: new ReadOnlyCollection<PackRunSimulationNode>(step.Children.Select(ConvertStep).ToList());
|
||||
|
||||
return new PackRunSimulationNode(
|
||||
step.Id,
|
||||
step.TemplateId,
|
||||
step.Kind,
|
||||
step.Enabled,
|
||||
step.Uses,
|
||||
step.ApprovalId,
|
||||
step.GateMessage,
|
||||
step.Parameters,
|
||||
step.MaxParallel,
|
||||
step.ContinueOnError,
|
||||
status,
|
||||
children);
|
||||
}
|
||||
|
||||
private static PackRunSimulationStatus DetermineStatus(PackRunExecutionStep step)
|
||||
{
|
||||
if (!step.Enabled)
|
||||
{
|
||||
return PackRunSimulationStatus.Skipped;
|
||||
}
|
||||
|
||||
return step.Kind switch
|
||||
{
|
||||
PackRunStepKind.GateApproval => PackRunSimulationStatus.RequiresApproval,
|
||||
PackRunStepKind.GatePolicy => PackRunSimulationStatus.RequiresPolicy,
|
||||
_ => PackRunSimulationStatus.Pending
|
||||
};
|
||||
}
|
||||
|
||||
private static IReadOnlyList<PackRunSimulationOutput> BuildOutputs(IReadOnlyList<TaskPackPlanOutput> outputs)
|
||||
{
|
||||
if (outputs.Count == 0)
|
||||
{
|
||||
return PackRunSimulationOutput.Empty;
|
||||
}
|
||||
|
||||
var list = new List<PackRunSimulationOutput>(outputs.Count);
|
||||
foreach (var output in outputs)
|
||||
{
|
||||
list.Add(new PackRunSimulationOutput(output.Name, output.Type, output.Path, output.Expression));
|
||||
}
|
||||
|
||||
return new ReadOnlyCollection<PackRunSimulationOutput>(list);
|
||||
}
|
||||
}
|
||||
using System.Collections.ObjectModel;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
|
||||
namespace StellaOps.TaskRunner.Core.Execution.Simulation;
|
||||
|
||||
public sealed class PackRunSimulationEngine
|
||||
{
|
||||
private readonly PackRunExecutionGraphBuilder graphBuilder;
|
||||
|
||||
public PackRunSimulationEngine()
|
||||
{
|
||||
graphBuilder = new PackRunExecutionGraphBuilder();
|
||||
}
|
||||
|
||||
public PackRunSimulationResult Simulate(TaskPackPlan plan)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(plan);
|
||||
|
||||
var graph = graphBuilder.Build(plan);
|
||||
var steps = graph.Steps.Select(ConvertStep).ToList();
|
||||
var outputs = BuildOutputs(plan.Outputs);
|
||||
|
||||
return new PackRunSimulationResult(steps, outputs, graph.FailurePolicy);
|
||||
}
|
||||
|
||||
private static PackRunSimulationNode ConvertStep(PackRunExecutionStep step)
|
||||
{
|
||||
var status = DetermineStatus(step);
|
||||
var children = step.Children.Count == 0
|
||||
? PackRunSimulationNode.Empty
|
||||
: new ReadOnlyCollection<PackRunSimulationNode>(step.Children.Select(ConvertStep).ToList());
|
||||
|
||||
return new PackRunSimulationNode(
|
||||
step.Id,
|
||||
step.TemplateId,
|
||||
step.Kind,
|
||||
step.Enabled,
|
||||
step.Uses,
|
||||
step.ApprovalId,
|
||||
step.GateMessage,
|
||||
step.Parameters,
|
||||
step.MaxParallel,
|
||||
step.ContinueOnError,
|
||||
status,
|
||||
children);
|
||||
}
|
||||
|
||||
private static PackRunSimulationStatus DetermineStatus(PackRunExecutionStep step)
|
||||
{
|
||||
if (!step.Enabled)
|
||||
{
|
||||
return PackRunSimulationStatus.Skipped;
|
||||
}
|
||||
|
||||
return step.Kind switch
|
||||
{
|
||||
PackRunStepKind.GateApproval => PackRunSimulationStatus.RequiresApproval,
|
||||
PackRunStepKind.GatePolicy => PackRunSimulationStatus.RequiresPolicy,
|
||||
_ => PackRunSimulationStatus.Pending
|
||||
};
|
||||
}
|
||||
|
||||
private static IReadOnlyList<PackRunSimulationOutput> BuildOutputs(IReadOnlyList<TaskPackPlanOutput> outputs)
|
||||
{
|
||||
if (outputs.Count == 0)
|
||||
{
|
||||
return PackRunSimulationOutput.Empty;
|
||||
}
|
||||
|
||||
var list = new List<PackRunSimulationOutput>(outputs.Count);
|
||||
foreach (var output in outputs)
|
||||
{
|
||||
list.Add(new PackRunSimulationOutput(output.Name, output.Type, output.Path, output.Expression));
|
||||
}
|
||||
|
||||
return new ReadOnlyCollection<PackRunSimulationOutput>(list);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,131 +1,131 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
|
||||
namespace StellaOps.TaskRunner.Core.Execution.Simulation;
|
||||
|
||||
public sealed class PackRunSimulationResult
|
||||
{
|
||||
public PackRunSimulationResult(
|
||||
IReadOnlyList<PackRunSimulationNode> steps,
|
||||
IReadOnlyList<PackRunSimulationOutput> outputs,
|
||||
TaskPackPlanFailurePolicy failurePolicy)
|
||||
{
|
||||
Steps = steps ?? throw new ArgumentNullException(nameof(steps));
|
||||
Outputs = outputs ?? throw new ArgumentNullException(nameof(outputs));
|
||||
FailurePolicy = failurePolicy ?? throw new ArgumentNullException(nameof(failurePolicy));
|
||||
}
|
||||
|
||||
public IReadOnlyList<PackRunSimulationNode> Steps { get; }
|
||||
|
||||
public IReadOnlyList<PackRunSimulationOutput> Outputs { get; }
|
||||
|
||||
public TaskPackPlanFailurePolicy FailurePolicy { get; }
|
||||
|
||||
public bool HasPendingApprovals => Steps.Any(ContainsApprovalRequirement);
|
||||
|
||||
private static bool ContainsApprovalRequirement(PackRunSimulationNode node)
|
||||
{
|
||||
if (node.Status is PackRunSimulationStatus.RequiresApproval or PackRunSimulationStatus.RequiresPolicy)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return node.Children.Any(ContainsApprovalRequirement);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PackRunSimulationNode
|
||||
{
|
||||
public PackRunSimulationNode(
|
||||
string id,
|
||||
string templateId,
|
||||
PackRunStepKind kind,
|
||||
bool enabled,
|
||||
string? uses,
|
||||
string? approvalId,
|
||||
string? gateMessage,
|
||||
IReadOnlyDictionary<string, TaskPackPlanParameterValue> parameters,
|
||||
int? maxParallel,
|
||||
bool continueOnError,
|
||||
PackRunSimulationStatus status,
|
||||
IReadOnlyList<PackRunSimulationNode> children)
|
||||
{
|
||||
Id = string.IsNullOrWhiteSpace(id) ? throw new ArgumentException("Value cannot be null or whitespace.", nameof(id)) : id;
|
||||
TemplateId = string.IsNullOrWhiteSpace(templateId) ? throw new ArgumentException("Value cannot be null or whitespace.", nameof(templateId)) : templateId;
|
||||
Kind = kind;
|
||||
Enabled = enabled;
|
||||
Uses = uses;
|
||||
ApprovalId = approvalId;
|
||||
GateMessage = gateMessage;
|
||||
Parameters = parameters ?? throw new ArgumentNullException(nameof(parameters));
|
||||
MaxParallel = maxParallel;
|
||||
ContinueOnError = continueOnError;
|
||||
Status = status;
|
||||
Children = children ?? throw new ArgumentNullException(nameof(children));
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public string TemplateId { get; }
|
||||
|
||||
public PackRunStepKind Kind { get; }
|
||||
|
||||
public bool Enabled { get; }
|
||||
|
||||
public string? Uses { get; }
|
||||
|
||||
public string? ApprovalId { get; }
|
||||
|
||||
public string? GateMessage { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, TaskPackPlanParameterValue> Parameters { get; }
|
||||
|
||||
public int? MaxParallel { get; }
|
||||
|
||||
public bool ContinueOnError { get; }
|
||||
|
||||
public PackRunSimulationStatus Status { get; }
|
||||
|
||||
public IReadOnlyList<PackRunSimulationNode> Children { get; }
|
||||
|
||||
public static IReadOnlyList<PackRunSimulationNode> Empty { get; } =
|
||||
new ReadOnlyCollection<PackRunSimulationNode>(Array.Empty<PackRunSimulationNode>());
|
||||
}
|
||||
|
||||
public enum PackRunSimulationStatus
|
||||
{
|
||||
Pending = 0,
|
||||
Skipped,
|
||||
RequiresApproval,
|
||||
RequiresPolicy
|
||||
}
|
||||
|
||||
public sealed class PackRunSimulationOutput
|
||||
{
|
||||
public PackRunSimulationOutput(
|
||||
string name,
|
||||
string type,
|
||||
TaskPackPlanParameterValue? path,
|
||||
TaskPackPlanParameterValue? expression)
|
||||
{
|
||||
Name = string.IsNullOrWhiteSpace(name) ? throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)) : name;
|
||||
Type = string.IsNullOrWhiteSpace(type) ? throw new ArgumentException("Value cannot be null or whitespace.", nameof(type)) : type;
|
||||
Path = path;
|
||||
Expression = expression;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string Type { get; }
|
||||
|
||||
public TaskPackPlanParameterValue? Path { get; }
|
||||
|
||||
public TaskPackPlanParameterValue? Expression { get; }
|
||||
|
||||
public bool RequiresRuntimeValue =>
|
||||
(Path?.RequiresRuntimeValue ?? false) ||
|
||||
(Expression?.RequiresRuntimeValue ?? false);
|
||||
|
||||
public static IReadOnlyList<PackRunSimulationOutput> Empty { get; } =
|
||||
new ReadOnlyCollection<PackRunSimulationOutput>(Array.Empty<PackRunSimulationOutput>());
|
||||
}
|
||||
using System.Collections.ObjectModel;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
|
||||
namespace StellaOps.TaskRunner.Core.Execution.Simulation;
|
||||
|
||||
public sealed class PackRunSimulationResult
|
||||
{
|
||||
public PackRunSimulationResult(
|
||||
IReadOnlyList<PackRunSimulationNode> steps,
|
||||
IReadOnlyList<PackRunSimulationOutput> outputs,
|
||||
TaskPackPlanFailurePolicy failurePolicy)
|
||||
{
|
||||
Steps = steps ?? throw new ArgumentNullException(nameof(steps));
|
||||
Outputs = outputs ?? throw new ArgumentNullException(nameof(outputs));
|
||||
FailurePolicy = failurePolicy ?? throw new ArgumentNullException(nameof(failurePolicy));
|
||||
}
|
||||
|
||||
public IReadOnlyList<PackRunSimulationNode> Steps { get; }
|
||||
|
||||
public IReadOnlyList<PackRunSimulationOutput> Outputs { get; }
|
||||
|
||||
public TaskPackPlanFailurePolicy FailurePolicy { get; }
|
||||
|
||||
public bool HasPendingApprovals => Steps.Any(ContainsApprovalRequirement);
|
||||
|
||||
private static bool ContainsApprovalRequirement(PackRunSimulationNode node)
|
||||
{
|
||||
if (node.Status is PackRunSimulationStatus.RequiresApproval or PackRunSimulationStatus.RequiresPolicy)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return node.Children.Any(ContainsApprovalRequirement);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PackRunSimulationNode
|
||||
{
|
||||
public PackRunSimulationNode(
|
||||
string id,
|
||||
string templateId,
|
||||
PackRunStepKind kind,
|
||||
bool enabled,
|
||||
string? uses,
|
||||
string? approvalId,
|
||||
string? gateMessage,
|
||||
IReadOnlyDictionary<string, TaskPackPlanParameterValue> parameters,
|
||||
int? maxParallel,
|
||||
bool continueOnError,
|
||||
PackRunSimulationStatus status,
|
||||
IReadOnlyList<PackRunSimulationNode> children)
|
||||
{
|
||||
Id = string.IsNullOrWhiteSpace(id) ? throw new ArgumentException("Value cannot be null or whitespace.", nameof(id)) : id;
|
||||
TemplateId = string.IsNullOrWhiteSpace(templateId) ? throw new ArgumentException("Value cannot be null or whitespace.", nameof(templateId)) : templateId;
|
||||
Kind = kind;
|
||||
Enabled = enabled;
|
||||
Uses = uses;
|
||||
ApprovalId = approvalId;
|
||||
GateMessage = gateMessage;
|
||||
Parameters = parameters ?? throw new ArgumentNullException(nameof(parameters));
|
||||
MaxParallel = maxParallel;
|
||||
ContinueOnError = continueOnError;
|
||||
Status = status;
|
||||
Children = children ?? throw new ArgumentNullException(nameof(children));
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
|
||||
public string TemplateId { get; }
|
||||
|
||||
public PackRunStepKind Kind { get; }
|
||||
|
||||
public bool Enabled { get; }
|
||||
|
||||
public string? Uses { get; }
|
||||
|
||||
public string? ApprovalId { get; }
|
||||
|
||||
public string? GateMessage { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, TaskPackPlanParameterValue> Parameters { get; }
|
||||
|
||||
public int? MaxParallel { get; }
|
||||
|
||||
public bool ContinueOnError { get; }
|
||||
|
||||
public PackRunSimulationStatus Status { get; }
|
||||
|
||||
public IReadOnlyList<PackRunSimulationNode> Children { get; }
|
||||
|
||||
public static IReadOnlyList<PackRunSimulationNode> Empty { get; } =
|
||||
new ReadOnlyCollection<PackRunSimulationNode>(Array.Empty<PackRunSimulationNode>());
|
||||
}
|
||||
|
||||
public enum PackRunSimulationStatus
|
||||
{
|
||||
Pending = 0,
|
||||
Skipped,
|
||||
RequiresApproval,
|
||||
RequiresPolicy
|
||||
}
|
||||
|
||||
public sealed class PackRunSimulationOutput
|
||||
{
|
||||
public PackRunSimulationOutput(
|
||||
string name,
|
||||
string type,
|
||||
TaskPackPlanParameterValue? path,
|
||||
TaskPackPlanParameterValue? expression)
|
||||
{
|
||||
Name = string.IsNullOrWhiteSpace(name) ? throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)) : name;
|
||||
Type = string.IsNullOrWhiteSpace(type) ? throw new ArgumentException("Value cannot be null or whitespace.", nameof(type)) : type;
|
||||
Path = path;
|
||||
Expression = expression;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string Type { get; }
|
||||
|
||||
public TaskPackPlanParameterValue? Path { get; }
|
||||
|
||||
public TaskPackPlanParameterValue? Expression { get; }
|
||||
|
||||
public bool RequiresRuntimeValue =>
|
||||
(Path?.RequiresRuntimeValue ?? false) ||
|
||||
(Expression?.RequiresRuntimeValue ?? false);
|
||||
|
||||
public static IReadOnlyList<PackRunSimulationOutput> Empty { get; } =
|
||||
new ReadOnlyCollection<PackRunSimulationOutput>(Array.Empty<PackRunSimulationOutput>());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user