Files
git.stella-ops.org/src/ReleaseOrchestrator/__Agents/StellaOps.Agent.Ecs/EcsCapability.cs
2026-02-01 21:37:40 +02:00

224 lines
7.4 KiB
C#

using Amazon.CloudWatchLogs;
using Amazon.ECS;
using Amazon.ECS.Model;
using Microsoft.Extensions.Logging;
using StellaOps.Agent.Core.Capability;
using StellaOps.Agent.Core.Models;
using StellaOps.Agent.Ecs.Tasks;
using System.Text.Json;
namespace StellaOps.Agent.Ecs;
/// <summary>
/// Agent capability for managing AWS ECS services and tasks.
/// </summary>
public sealed class EcsCapability : IAgentCapability, IAsyncDisposable
{
private readonly IAmazonECS _ecsClient;
private readonly IAmazonCloudWatchLogs _logsClient;
private readonly TimeProvider _timeProvider;
private readonly ILoggerFactory _loggerFactory;
private readonly ILogger<EcsCapability> _logger;
private readonly Dictionary<string, Func<AgentTaskInfo, CancellationToken, Task<AgentTaskResult>>> _taskHandlers;
/// <summary>
/// Gets the capability name.
/// </summary>
public string Name => "ecs";
/// <summary>
/// Gets the capability version.
/// </summary>
public string Version => "1.0.0";
/// <summary>
/// Gets the supported task types.
/// </summary>
public IReadOnlyList<string> SupportedTaskTypes { get; } = new[]
{
"ecs.deploy",
"ecs.run",
"ecs.stop",
"ecs.scale",
"ecs.register",
"ecs.health",
"ecs.describe"
};
/// <summary>
/// Creates a new ECS capability.
/// </summary>
/// <param name="ecsClient">The ECS client.</param>
/// <param name="logsClient">The CloudWatch Logs client.</param>
/// <param name="timeProvider">Time provider for timestamps.</param>
/// <param name="loggerFactory">Logger factory.</param>
public EcsCapability(
IAmazonECS ecsClient,
IAmazonCloudWatchLogs logsClient,
TimeProvider timeProvider,
ILoggerFactory loggerFactory)
{
_ecsClient = ecsClient ?? throw new ArgumentNullException(nameof(ecsClient));
_logsClient = logsClient ?? throw new ArgumentNullException(nameof(logsClient));
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
_logger = loggerFactory.CreateLogger<EcsCapability>();
_taskHandlers = new Dictionary<string, Func<AgentTaskInfo, CancellationToken, Task<AgentTaskResult>>>
{
["ecs.deploy"] = ExecuteDeployAsync,
["ecs.run"] = ExecuteRunTaskAsync,
["ecs.stop"] = ExecuteStopTaskAsync,
["ecs.scale"] = ExecuteScaleAsync,
["ecs.register"] = ExecuteRegisterAsync,
["ecs.health"] = ExecuteHealthCheckAsync,
["ecs.describe"] = ExecuteDescribeAsync
};
}
/// <inheritdoc />
public async Task<bool> InitializeAsync(CancellationToken ct = default)
{
try
{
// Verify AWS credentials and ECS access by listing clusters
var response = await _ecsClient.ListClustersAsync(new ListClustersRequest
{
MaxResults = 1
}, ct);
_logger.LogInformation(
"ECS capability initialized, AWS API accessible");
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to initialize ECS capability - AWS API not accessible");
return false;
}
}
/// <inheritdoc />
public async Task<AgentTaskResult> ExecuteAsync(AgentTaskInfo task, CancellationToken ct = default)
{
if (!_taskHandlers.TryGetValue(task.TaskType, out var handler))
{
throw new InvalidEcsPayloadException(task.TaskType, "Unsupported task type");
}
var startTime = _timeProvider.GetUtcNow();
try
{
var result = await handler(task, ct);
return result with
{
Duration = _timeProvider.GetUtcNow() - startTime
};
}
catch (InvalidEcsPayloadException)
{
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "ECS task {TaskType} failed", task.TaskType);
return new AgentTaskResult
{
TaskId = task.Id,
Success = false,
Error = ex.Message,
CompletedAt = _timeProvider.GetUtcNow(),
Duration = _timeProvider.GetUtcNow() - startTime
};
}
}
/// <inheritdoc />
public async Task<CapabilityHealthStatus> CheckHealthAsync(CancellationToken ct = default)
{
try
{
await _ecsClient.ListClustersAsync(new ListClustersRequest { MaxResults = 1 }, ct);
return new CapabilityHealthStatus(true, "ECS capability ready");
}
catch (Exception ex)
{
return new CapabilityHealthStatus(false, $"ECS API not accessible: {ex.Message}");
}
}
private async Task<AgentTaskResult> ExecuteDeployAsync(AgentTaskInfo task, CancellationToken ct)
{
var taskHandler = new EcsDeployServiceTask(
_ecsClient,
_timeProvider,
_loggerFactory.CreateLogger<EcsDeployServiceTask>());
return await taskHandler.ExecuteAsync(task, ct);
}
private async Task<AgentTaskResult> ExecuteRunTaskAsync(AgentTaskInfo task, CancellationToken ct)
{
var taskHandler = new EcsRunTaskTask(
_ecsClient,
_timeProvider,
_loggerFactory.CreateLogger<EcsRunTaskTask>());
return await taskHandler.ExecuteAsync(task, ct);
}
private async Task<AgentTaskResult> ExecuteStopTaskAsync(AgentTaskInfo task, CancellationToken ct)
{
var taskHandler = new EcsStopTaskTask(
_ecsClient,
_timeProvider,
_loggerFactory.CreateLogger<EcsStopTaskTask>());
return await taskHandler.ExecuteAsync(task, ct);
}
private async Task<AgentTaskResult> ExecuteScaleAsync(AgentTaskInfo task, CancellationToken ct)
{
var taskHandler = new EcsScaleServiceTask(
_ecsClient,
_timeProvider,
_loggerFactory.CreateLogger<EcsScaleServiceTask>());
return await taskHandler.ExecuteAsync(task, ct);
}
private async Task<AgentTaskResult> ExecuteRegisterAsync(AgentTaskInfo task, CancellationToken ct)
{
var taskHandler = new EcsRegisterTaskDefinitionTask(
_ecsClient,
_timeProvider,
_loggerFactory.CreateLogger<EcsRegisterTaskDefinitionTask>());
return await taskHandler.ExecuteAsync(task, ct);
}
private async Task<AgentTaskResult> ExecuteHealthCheckAsync(AgentTaskInfo task, CancellationToken ct)
{
var taskHandler = new EcsHealthCheckTask(
_ecsClient,
_timeProvider,
_loggerFactory.CreateLogger<EcsHealthCheckTask>());
return await taskHandler.ExecuteAsync(task, ct);
}
private async Task<AgentTaskResult> ExecuteDescribeAsync(AgentTaskInfo task, CancellationToken ct)
{
var taskHandler = new EcsDescribeServiceTask(
_ecsClient,
_timeProvider,
_loggerFactory.CreateLogger<EcsDescribeServiceTask>());
return await taskHandler.ExecuteAsync(task, ct);
}
/// <inheritdoc />
public ValueTask DisposeAsync()
{
_ecsClient.Dispose();
_logsClient.Dispose();
return ValueTask.CompletedTask;
}
}