summaryrefslogtreecommitdiff
path: root/src/marlin.web/Controllers/Api
diff options
context:
space:
mode:
Diffstat (limited to 'src/marlin.web/Controllers/Api')
-rw-r--r--src/marlin.web/Controllers/Api/DispatchController.cs69
-rw-r--r--src/marlin.web/Controllers/Api/InstanceGrpcService.cs54
2 files changed, 123 insertions, 0 deletions
diff --git a/src/marlin.web/Controllers/Api/DispatchController.cs b/src/marlin.web/Controllers/Api/DispatchController.cs
new file mode 100644
index 0000000..076c4dd
--- /dev/null
+++ b/src/marlin.web/Controllers/Api/DispatchController.cs
@@ -0,0 +1,69 @@
+namespace marlin.web.Controllers.Api;
+
+using marlin.core.models;
+using marlin.core.util;
+using marlin.web.Data;
+using marlin.web.Models;
+using marlin.web.Util;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+
+[Route("api/dispatch")]
+[ApiController]
+[Authorize(Policy = "Agent")]
+public class DispatchController(ApplicationDbContext dbContext) : ControllerBase
+{
+ [HttpPost("handshake")]
+ public async Task<ActionResult<AgentConfiguration>> Handshake(AgentStatus status, CancellationToken ct)
+ {
+ var agentId = this.User.RequireAgentId();
+
+ var agent = await dbContext.Agents
+ .FirstAsync(x => x.Id == agentId, ct);
+
+ foreach (var capability in status.CapabilitySet.Items)
+ agent.Capabilities.Items[capability.Key] = capability.Value;
+
+ await dbContext.SaveChangesAsync(ct);
+
+ return new AgentConfiguration(
+ Name: agent.Name,
+ MaxConcurrentJobs: agent.MaxConcurrentJobs
+ );
+ }
+
+ [HttpGet("queue")]
+ public async Task<ActionResult<DispatchJob?>> Queue(CancellationToken ct)
+ {
+ var agentId = this.User.RequireAgentId();
+
+ var nextJob = await dbContext
+ .Jobs
+ .Include(x => x.Source)
+ .Include(x => x.Branch)
+ .Include(x => x.Commit)
+ .Where(x => x.Status == JobStatus.Pending && x.AgentId == null)
+ .OrderBy(x => x.CreatedAt)
+ .FirstOrDefaultAsync(ct);
+
+ if (nextJob == null)
+ return (DispatchJob?)null;
+
+ string claimToken = SecureTokenGenerator.CreateClaimToken();
+ nextJob.ClaimToken = claimToken;
+ nextJob.AgentId = agentId;
+
+ await dbContext.SaveChangesAsync(ct);
+
+ return new DispatchJob(
+ Id: nextJob.Id,
+ Token: claimToken,
+ Vcs: nextJob.Source.UpstreamUrl,
+ Ref: nextJob.Branch.Name,
+ Commit: nextJob.Commit.Hash,
+ Recipe: nextJob.Recipe
+ );
+ }
+}
+
diff --git a/src/marlin.web/Controllers/Api/InstanceGrpcService.cs b/src/marlin.web/Controllers/Api/InstanceGrpcService.cs
new file mode 100644
index 0000000..fb0af5a
--- /dev/null
+++ b/src/marlin.web/Controllers/Api/InstanceGrpcService.cs
@@ -0,0 +1,54 @@
+namespace marlin.web.Controllers.Api;
+
+using core.contracts;
+using core.models;
+using core.models.plan;
+using Data;
+using Grpc.Core;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using Models;
+using ProtoBuf.Grpc;
+using Util;
+
+[Authorize(Policy = "JobScoped")]
+public class InstanceGrpcService(ApplicationDbContext dbContext) : IInstanceService
+{
+ public async Task SendLogEvent(LogEventRequest request, CallContext context = default)
+ {
+ var user = context.ServerCallContext!.GetHttpContext().User;
+ var jobId = user.RequireJobId();
+
+ dbContext.Logs.Add(new LogEventRecord
+ {
+ At = request.At,
+ Message = request.Message,
+ Error = request.Error,
+ StepId = request.StepId,
+ JobId = jobId
+ });
+
+ await dbContext.SaveChangesAsync(context.CancellationToken);
+ }
+
+ public async Task SendStatusUpdate(StatusUpdateRequest request, CallContext context = default)
+ {
+ var user = context.ServerCallContext!.GetHttpContext().User;
+ var jobId = user.RequireJobId();
+
+ var job = await dbContext.Jobs
+ .FirstAsync(x => x.Id == jobId, context.CancellationToken);
+
+ job.Status = request.Status;
+
+ dbContext.JobStatusUpdates.Add(new JobStatusUpdate
+ {
+ At = DateTime.UtcNow,
+ Status = request.Status,
+ Job = job
+ });
+
+ await dbContext.SaveChangesAsync(context.CancellationToken);
+ }
+}