From ecd5f5d13936dd097b12766cb95a568ad5b073a9 Mon Sep 17 00:00:00 2001 From: Melodi <62381002+Melodi17@users.noreply.github.com> Date: Tue, 27 Oct 2026 23:04:59 +1000 Subject: feat: lots of stuff --- .../Controllers/Api/DispatchController.cs | 69 +++++++++++++++++++++ .../Controllers/Api/InstanceGrpcService.cs | 54 ++++++++++++++++ src/marlin.web/Controllers/DispatchController.cs | 71 ---------------------- src/marlin.web/Controllers/JobsController.cs | 4 ++ 4 files changed, 127 insertions(+), 71 deletions(-) create mode 100644 src/marlin.web/Controllers/Api/DispatchController.cs create mode 100644 src/marlin.web/Controllers/Api/InstanceGrpcService.cs delete mode 100644 src/marlin.web/Controllers/DispatchController.cs (limited to 'src/marlin.web/Controllers') 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> 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> 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); + } +} diff --git a/src/marlin.web/Controllers/DispatchController.cs b/src/marlin.web/Controllers/DispatchController.cs deleted file mode 100644 index a37d5fa..0000000 --- a/src/marlin.web/Controllers/DispatchController.cs +++ /dev/null @@ -1,71 +0,0 @@ -namespace marlin.web.Controllers; - -using core.models; -using core.models.meta; -using core.util; -using Data; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Models; -using Services; -using Util; - -[Route("api/dispatch")] -[ApiController] -[Authorize(Policy = "Agent")] -public class DispatchController(ApplicationDbContext dbContext) : ControllerBase -{ - [HttpPost("handshake")] - public async Task> Handshake(AgentStatus status, CancellationToken ct) - { - var agentId = 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> Queue(CancellationToken ct) - { - var agentId = 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/JobsController.cs b/src/marlin.web/Controllers/JobsController.cs index 552b56c..9aaf6eb 100644 --- a/src/marlin.web/Controllers/JobsController.cs +++ b/src/marlin.web/Controllers/JobsController.cs @@ -95,6 +95,10 @@ public class JobsController(ApplicationDbContext dbContext) : Controller .Include(x => x.Agent) .Include(x => x.Source) .Include(x => x.StatusUpdates) + .Include(x => x.Logs) + .Include(x => x.Branch) + .Include(x => x.Commit) + .AsSplitQuery() .FirstOrDefaultAsync(x => x.Id == id); if (job == null) -- cgit v1.3.1