224 lines
7.4 KiB
C#
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;
|
|
}
|
|
}
|