diff --git a/src/Runner.Server/Controllers/ActionDownloadInfoController.cs b/src/Runner.Server/Controllers/ActionDownloadInfoController.cs index 3a06f8826ed..e5f2d74ec86 100644 --- a/src/Runner.Server/Controllers/ActionDownloadInfoController.cs +++ b/src/Runner.Server/Controllers/ActionDownloadInfoController.cs @@ -96,7 +96,7 @@ public IActionResult GetLocalCheckout([FromQuery] string format, [FromQuery] str } [HttpPost("{scopeIdentifier}/{hubName}/{planId}")] - public async Task Get(Guid scopeIdentifier, string hubName, Guid planId) + public async Task Get(Guid scopeIdentifier, string hubName, Guid planId, [FromBody, Vss] ActionReferenceList reflist) { var localcheckout = User.FindFirst("localcheckout")?.Value ?? ""; var runid = User.FindFirst("runid")?.Value ?? ""; @@ -104,7 +104,6 @@ public async Task Get(Guid scopeIdentifier, string hubName, Guid if(!string.IsNullOrEmpty(token)) { GITHUB_TOKEN = token; } - ActionReferenceList reflist = await FromBody(); var repository = User.FindFirst("repository")?.Value; var defGhToken = await CreateGithubAppToken(repository); try { diff --git a/src/Runner.Server/Controllers/AgentController.cs b/src/Runner.Server/Controllers/AgentController.cs index 5facac92092..1adbb6a1d8d 100644 --- a/src/Runner.Server/Controllers/AgentController.cs +++ b/src/Runner.Server/Controllers/AgentController.cs @@ -16,6 +16,7 @@ using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Runner.Server.Models; +using Swashbuckle.AspNetCore.Annotations; namespace Runner.Server.Controllers { @@ -39,8 +40,8 @@ public AgentController(IMemoryCache cache, SqLiteDb context, IConfiguration conf [HttpPost("{poolId}")] [Authorize(AuthenticationSchemes = "Bearer", Policy = "AgentManagement")] - public async Task Post(int poolId) { - TaskAgent agent = await FromBody(); + [SwaggerResponse(200, type: typeof(TaskAgent))] + public async Task Post(int poolId, [FromBody, Vss] TaskAgent agent) { lock(lok) { // Without a lock we get rsa message exchange problems, decryption error of rsa encrypted session aes key agent.Authorization.AuthorizationUrl = new Uri(new Uri(ServerUrl), "_apis/v1/auth/"); @@ -52,6 +53,7 @@ public async Task Post(int poolId) { } [HttpGet("{poolId}/{agentId}")] + [SwaggerResponse(200, type: typeof(TaskAgent))] public async Task Get(int poolId, int agentId) { return await Ok(Agent.GetAgent(_cache, _context, poolId, agentId).TaskAgent); @@ -71,9 +73,8 @@ public IActionResult Delete(int poolId, int agentId) [HttpPut("{poolId}/{agentId}")] [Authorize(AuthenticationSchemes = "Bearer", Policy = "AgentManagement")] - public async Task Replace(int poolId, int agentId) + public async Task Replace(int poolId, int agentId, [FromBody, Vss] TaskAgent tagent) { - TaskAgent tagent = await FromBody(); lock(lok) { var agent = Agent.GetAgent(_cache, _context, poolId, agentId); agent.TaskAgent.Authorization = new TaskAgentAuthorization() { ClientId = agent.ClientId, PublicKey = new TaskAgentPublicKey(agent.Exponent, agent.Modulus), AuthorizationUrl = new Uri(new Uri(ServerUrl), "_apis/v1/auth/") }; @@ -105,6 +106,7 @@ public async Task Replace(int poolId, int agentId) } [HttpGet("{poolId}")] + [SwaggerResponse(200, type: typeof(VssJsonCollectionWrapper>))] public async Task Get(int poolId, [FromQuery] string agentName) { return await Ok(new VssJsonCollectionWrapper>(from agent in _context.Agents where (poolId == 0 || poolId == -1 || agent.Pool.Id == poolId) && (string.IsNullOrEmpty(agentName) || agent.TaskAgent.Name == agentName) select agent.TaskAgent)); diff --git a/src/Runner.Server/Controllers/AgentPoolsController.cs b/src/Runner.Server/Controllers/AgentPoolsController.cs index 89084265a1a..ad4b3a499d4 100644 --- a/src/Runner.Server/Controllers/AgentPoolsController.cs +++ b/src/Runner.Server/Controllers/AgentPoolsController.cs @@ -12,6 +12,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Runner.Server.Models; +using Swashbuckle.AspNetCore.Annotations; namespace Runner.Server.Controllers { @@ -42,6 +43,7 @@ public AgentPoolsController(IMemoryCache cache, SqLiteDb db, IConfiguration conf } [HttpGet] + [SwaggerResponse(200, type: typeof(VssJsonCollectionWrapper>))] public Task Get(string poolName = "", string properties = "", string poolType = "") { return Ok((from pool in db.Pools.Include(a => a.TaskAgentPool).AsEnumerable() select pool.TaskAgentPool).ToList()); diff --git a/src/Runner.Server/Controllers/AgentRequestController.cs b/src/Runner.Server/Controllers/AgentRequestController.cs index ef493cf0f99..90acb7c1cf5 100644 --- a/src/Runner.Server/Controllers/AgentRequestController.cs +++ b/src/Runner.Server/Controllers/AgentRequestController.cs @@ -39,9 +39,8 @@ public Task GetAgentRequest(int poolId, long requestId, string } [HttpPatch("{poolId}/{requestId}")] - public async Task UpdateAgentRequest(int poolId, long requestId) + public async Task UpdateAgentRequest(int poolId, long requestId, [FromBody, Vss] TaskAgentJobRequest patch) { - var patch = await FromBody(); patch.LockedUntil = DateTime.UtcNow.AddDays(1); return await Ok(patch); } diff --git a/src/Runner.Server/Controllers/AgentSessionController.cs b/src/Runner.Server/Controllers/AgentSessionController.cs index 7ac723af858..7c0579c712e 100644 --- a/src/Runner.Server/Controllers/AgentSessionController.cs +++ b/src/Runner.Server/Controllers/AgentSessionController.cs @@ -14,6 +14,7 @@ using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Runner.Server.Models; +using Swashbuckle.AspNetCore.Annotations; namespace Runner.Server.Controllers { @@ -36,9 +37,9 @@ public AgentSessionController(IMemoryCache cache, SqLiteDb context, IConfigurati } [HttpPost("{poolId}")] - public async Task Create(int poolId) + [SwaggerResponse(200, type: typeof(TaskAgentSession))] + public async Task Create(int poolId, [FromBody, Vss] TaskAgentSession session) { - var session = await FromBody(); session.SessionId = Guid.NewGuid(); var aes = Aes.Create(); Agent agent = Agent.GetAgent(_cache, _context, poolId, session.Agent.Id); diff --git a/src/Runner.Server/Controllers/ArtifactController.cs b/src/Runner.Server/Controllers/ArtifactController.cs index c25b63cf1b8..8654939abff 100644 --- a/src/Runner.Server/Controllers/ArtifactController.cs +++ b/src/Runner.Server/Controllers/ArtifactController.cs @@ -59,7 +59,7 @@ public ArtifactController(SqLiteDb context, IConfiguration configuration) : base Directory.CreateDirectory(_targetFilePath); } - public async Task CreateContainer(long run, long attempt, CreateActionsStorageArtifactParameters req, long artifactsMinAttempt = -1) { + internal async Task CreateContainer(long run, long attempt, CreateActionsStorageArtifactParameters req, long artifactsMinAttempt = -1) { var filecontainer = (from fileContainer in _context.ArtifactFileContainer where fileContainer.Container.Attempt.Attempt == attempt && fileContainer.Container.Attempt.WorkflowRun.Id == run && fileContainer.Id == req.ContainerId select fileContainer).Include(f => f.Container).Include(f => f.Files).FirstOrDefault(); if(filecontainer != null) { var files = filecontainer.Files; @@ -99,8 +99,7 @@ public async Task CreateContainer(long run, long attempt, } [HttpPost("{run}/artifacts")] - public async Task CreateContainer(long run) { - var req = await FromBody(); + public async Task CreateContainer(long run, [FromBody, Vss] CreateActionsStorageArtifactParameters req) { var attempt = Int64.Parse(User.FindFirst("attempt")?.Value ?? "1"); var artifactsMinAttempt = Int64.Parse(User.FindFirst("artifactsMinAttempt")?.Value ?? "-1"); // azp build artifact / parse "{\"id\":0,\"name\":\"drop\",\"source\":null,\"resource\":{\"type\":\"Container\",\"data\":\"#/10/drop\",\"properties\":{\"localpath\":\"/home/christopher/.local/share/gharun/a/l53fnlmg.djp/w/1/a\",\"artifactsize\":\"28710\"}}}" @@ -112,8 +111,7 @@ public async Task CreateContainer(long run) { } [HttpPatch("{run}/artifacts")] - public async Task PatchContainer(long run, [FromQuery] string artifactName) { - var req = await FromBody(); + public async Task PatchContainer(long run, [FromQuery] string artifactName, [FromBody, Vss] CreateActionsStorageArtifactParameters req) { var attempt = Int64.Parse(User.FindFirst("attempt")?.Value ?? "1"); var artifactsMinAttempt = Int64.Parse(User.FindFirst("artifactsMinAttempt")?.Value ?? "-1"); var container = (from fileContainer in _context.ArtifactFileContainer where (fileContainer.Container.Attempt.Attempt >= artifactsMinAttempt || artifactsMinAttempt == -1) && fileContainer.Container.Attempt.Attempt <= attempt && fileContainer.Container.Attempt.WorkflowRun.Id == run && fileContainer.Name.ToLower() == artifactName.ToLower() select fileContainer).First(); diff --git a/src/Runner.Server/Controllers/CacheController.cs b/src/Runner.Server/Controllers/CacheController.cs index 6d6f03aab07..e2e5bbf69fc 100644 --- a/src/Runner.Server/Controllers/CacheController.cs +++ b/src/Runner.Server/Controllers/CacheController.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Configuration; using Runner.Server.Models; +using Swashbuckle.AspNetCore.Annotations; namespace Runner.Server.Controllers { @@ -34,7 +35,7 @@ private class ReserveCacheResponse { public int cacheId {get;set;} } - private class ReserveCacheRequest { + public class ReserveCacheRequest { public string key {get;set;} public string version {get;set;} } @@ -47,8 +48,8 @@ public CacheController(SqLiteDb context, IWebHostEnvironment environment, IConfi } [HttpPost("caches")] - public async Task ReserveCache(string owner, string repo) { - var req = await FromBody(); + [SwaggerResponse(200, type: typeof(ReserveCacheResponse))] + public async Task ReserveCache(string owner, string repo, [FromBody, Vss] ReserveCacheRequest req) { var filename = Path.GetRandomFileName(); var reference = User.FindFirst("ref")?.Value ?? "refs/heads/main"; var repository = User.FindFirst("repository")?.Value ?? "Unknown/Unknown"; @@ -59,6 +60,7 @@ public async Task ReserveCache(string owner, string repo) { } [HttpGet("cache")] + [SwaggerResponse(200, type: typeof(ArtifactCacheEntry))] public async Task GetCacheEntry( string owner, string repo, [FromQuery] string keys, [FromQuery] string version) { var a = keys.Split(','); var defaultRef = User.FindFirst("defaultRef")?.Value ?? "refs/heads/main"; diff --git a/src/Runner.Server/Controllers/ConnectionDataController.cs b/src/Runner.Server/Controllers/ConnectionDataController.cs index 79f493c70aa..2b393d13af4 100644 --- a/src/Runner.Server/Controllers/ConnectionDataController.cs +++ b/src/Runner.Server/Controllers/ConnectionDataController.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Swashbuckle.AspNetCore.Annotations; namespace Runner.Server.Controllers { @@ -22,6 +23,7 @@ public ConnectionDataController(IConfiguration conf) : base(conf) { [HttpGet] [AllowAnonymous] + [SwaggerResponse(200, type: typeof(ConnectionData))] public async Task Get() { return await Ok(new ConnectionData() { diff --git a/src/Runner.Server/Controllers/FinishJobController.cs b/src/Runner.Server/Controllers/FinishJobController.cs index 46d87688a5e..9f20cc1a09d 100644 --- a/src/Runner.Server/Controllers/FinishJobController.cs +++ b/src/Runner.Server/Controllers/FinishJobController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -38,7 +38,7 @@ public FinishJobController(IMemoryCache cache, SqLiteDb context, IConfiguration public static event JobAssigned OnJobAssigned; public static event JobStarted OnJobStarted; - public void InvokeJobCompleted(JobCompletedEvent ev) { + internal void InvokeJobCompleted(JobCompletedEvent ev) { try { { var job = _cache.Get(ev.JobId); @@ -81,9 +81,8 @@ public void InvokeJobCompleted(JobCompletedEvent ev) { [HttpPost("{scopeIdentifier}/{hubName}/{planId}")] [Authorize(AuthenticationSchemes = "Bearer", Policy = "AgentJob")] - public async Task OnEvent(Guid scopeIdentifier, string hubName, Guid planId) + public IActionResult OnEvent(Guid scopeIdentifier, string hubName, Guid planId, [FromBody, Vss] JobEvent jevent) { - var jevent = await FromBody(); if (jevent is JobCompletedEvent ev) { InvokeJobCompleted(ev); return Ok(); diff --git a/src/Runner.Server/Controllers/LogfilesController.cs b/src/Runner.Server/Controllers/LogfilesController.cs index 82eba00b2a3..f2dd5a7f7d1 100644 --- a/src/Runner.Server/Controllers/LogfilesController.cs +++ b/src/Runner.Server/Controllers/LogfilesController.cs @@ -15,6 +15,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Runner.Server.Models; +using Swashbuckle.AspNetCore.Annotations; namespace Runner.Server.Controllers { @@ -31,9 +32,9 @@ public LogfilesController(SqLiteDb context, IConfiguration conf) : base(conf) } [HttpPost("{scopeIdentifier}/{hubName}/{planId}")] [Authorize(AuthenticationSchemes = "Bearer", Policy = "AgentJob")] - public async Task CreateLog(Guid scopeIdentifier, string hubName, Guid planId) + [SwaggerResponse(200, type: typeof(TaskLog))] + public async Task CreateLog(Guid scopeIdentifier, string hubName, Guid planId, [FromBody, Vss] TaskLog log) { - var log = await FromBody(); _context.Logs.Add(new SqLiteDb.LogStorage() { Ref = log }); await _context.SaveChangesAsync(); return await Ok(log); diff --git a/src/Runner.Server/Controllers/MessageController.cs b/src/Runner.Server/Controllers/MessageController.cs index 83349ba8659..d184880cc51 100644 --- a/src/Runner.Server/Controllers/MessageController.cs +++ b/src/Runner.Server/Controllers/MessageController.cs @@ -46,6 +46,7 @@ using System.Reflection; using Quartz; using Quartz.Impl.Matchers; +using Swashbuckle.AspNetCore.Annotations; namespace Runner.Server.Controllers { @@ -4866,7 +4867,7 @@ public async Task UploadFile(Guid id) { sh.Token.Cancel(); } - public async Task GetFile(long runid, string path, string repository = null) { + internal async Task GetFile(long runid, string path, string repository = null) { var sh = new shared2(); sh.Token = new CancellationTokenSource(20 * 1000); Guid id = Guid.NewGuid(); @@ -4877,7 +4878,7 @@ public async Task GetFile(long runid, string path, string repository = n return sh.Content; } - public bool TryGetFile(long runid, string path, out string content, string repository = null) { + internal bool TryGetFile(long runid, string path, out string content, string repository = null) { content = GetFile(runid, path, repository).GetAwaiter().GetResult(); return content != null; } @@ -4892,7 +4893,7 @@ public void UpRepoExists(Guid id) { sh.Token.Cancel(); } - public async Task RepoExists(long runid, string repository = null) { + internal async Task RepoExists(long runid, string repository = null) { var sh = new shared2(); sh.Token = new CancellationTokenSource(20 * 1000); Guid id = Guid.NewGuid(); @@ -6395,6 +6396,9 @@ public async Task IsAgentOnline([FromQuery] string name) [HttpGet("{poolId}")] [Authorize(AuthenticationSchemes = "Bearer", Policy = "Agent")] + [SwaggerResponse(200, type: typeof(TaskAgentMessage))] + [SwaggerResponse(204, "No message for 50s")] + [SwaggerResponse(403, type: typeof(WrappedException))] public async Task GetMessage(int poolId, Guid sessionId) { Session session; @@ -7219,6 +7223,7 @@ public async Task OnSchedule2([FromQuery] string job, [FromQuery] private static ConcurrentDictionary initializingJobs = new ConcurrentDictionary(); [HttpGet] + [SwaggerResponse(200, type: typeof(Job[]))] public async Task GetJobs([FromQuery] string repo, [FromQuery] long[] runid, [FromQuery] int? depending, [FromQuery] Guid? jobid, [FromQuery] int? page) { if(jobid != null) { var j = GetJob(jobid.Value); @@ -7235,41 +7240,48 @@ public async Task GetJobs([FromQuery] string repo, [FromQuery] lo } [HttpGet("owners")] + [SwaggerResponse(200, type: typeof(Owner[]))] public async Task GetOwners([FromQuery] int? page) { var query = from j in _context.Set() orderby j.Id descending select j; return await Ok(page.HasValue ? query.Skip(page.Value * 30).Take(30) : query, true); } [HttpGet("repositories")] + [SwaggerResponse(200, type: typeof(Repository[]))] public async Task GetRepositories([FromQuery] int? page, [FromQuery] string owner) { var query = from j in _context.Set() where j.Owner.Name.ToLower() == owner.ToLower() orderby j.Id descending select j; return await Ok(page.HasValue ? query.Skip(page.Value * 30).Take(30) : query, true); } [HttpGet("workflow/runs")] + [SwaggerResponse(200, type: typeof(WorkflowRun[]))] public async Task GetWorkflows([FromQuery] int? page, [FromQuery] string owner, [FromQuery] string repo) { var query = (from run in _context.Set() from attempt in _context.Set() where run.Id == attempt.WorkflowRun.Id && attempt.Attempt == (from a in _context.Set() where run.Id == a.WorkflowRun.Id orderby a.Attempt descending select a.Attempt).First() && (string.IsNullOrEmpty(owner) || run.Workflow.Repository.Owner.Name.ToLower() == owner.ToLower()) && (string.IsNullOrEmpty(repo) || run.Workflow.Repository.Name.ToLower() == repo.ToLower()) orderby run.Id descending select new WorkflowRun() { EventName = attempt.EventName, Ref = attempt.Ref, Sha = attempt.Sha, Result = attempt.Result, FileName = run.FileName, DisplayName = run.DisplayName, Id = run.Id, Owner = run.Workflow.Repository.Owner.Name, Repo = run.Workflow.Repository.Name}); return await Ok(page.HasValue ? query.Skip(page.Value * 30).Take(30) : query, true); } [HttpGet("workflow/run/{id}")] + [SwaggerResponse(200, type: typeof(WorkflowRun))] public async Task GetWorkflows(long id) { return await Ok((from r in _context.Set() where r.Id == id select r).First(), true); } [HttpGet("workflow/run/{id}/attempts")] + [SwaggerResponse(200, type: typeof(WorkflowRunAttempt[]))] public async Task GetWorkflowAttempts(long id, [FromQuery] int? page) { var query = from r in _context.Set() where r.WorkflowRun.Id == id select r; return await Ok(page.HasValue ? query.Skip(page.Value * 30).Take(30) : query, true); } [HttpGet("workflow/run/{id}/attempt/{attempt}")] + [SwaggerResponse(200, type: typeof(WorkflowRunAttempt))] public async Task GetWorkflows(long id, int attempt) { var query = from r in _context.Set() where r.WorkflowRun.Id == id && r.Attempt == attempt select r; return await Ok(query.First(), true); } [HttpGet("workflow/run/{id}/attempt/{attempt}/jobs")] + [SwaggerResponse(200, type: typeof(Job[]))] public async Task GetWorkflowAttempts(long id, int attempt, [FromQuery] int? page) { var query = from j in _context.Jobs where j.WorkflowRunAttempt.WorkflowRun.Id == id && j.WorkflowRunAttempt.Attempt == attempt select j; return await Ok(page.HasValue ? query.Skip(page.Value * 30).Take(30) : query, true); @@ -7515,6 +7527,7 @@ public class WorkflowEventArgs { public static event Action workflowevent; [HttpGet("event")] + [SwaggerResponse(200, contentTypes: new[]{"text/event-stream"})] public IActionResult Message(string owner, string repo, [FromQuery] string filter, [FromQuery] long? runid) { var mfilter = new WorkflowPattern(filter ?? (owner + "/" + repo), RegexOptions.IgnoreCase); @@ -7556,6 +7569,7 @@ public IActionResult Message(string owner, string repo, [FromQuery] string filte } [HttpGet("event2")] + [SwaggerResponse(200, contentTypes: new[]{"text/event-stream"})] public IActionResult LiveUpdateEvents([FromQuery] string owner, [FromQuery] string repo, [FromQuery] long? runid) { var requestAborted = HttpContext.RequestAborted; diff --git a/src/Runner.Server/Controllers/RunnerRegistrationController.cs b/src/Runner.Server/Controllers/RunnerRegistrationController.cs index cbb109a5e46..146abb67658 100644 --- a/src/Runner.Server/Controllers/RunnerRegistrationController.cs +++ b/src/Runner.Server/Controllers/RunnerRegistrationController.cs @@ -18,6 +18,7 @@ using Microsoft.Extensions.Primitives; using Microsoft.IdentityModel.Tokens; using Runner.Server.Models; +using Swashbuckle.AspNetCore.Annotations; namespace Runner.Server.Controllers { @@ -32,7 +33,7 @@ public RunnerRegistrationController(IConfiguration configuration) : base(configu RUNNER_TOKEN = configuration.GetSection("Runner.Server")?.GetValue("RUNNER_TOKEN") ?? ""; } - class AddRemoveRunner + public class AddRemoveRunner { [DataMember(Name = "url")] public string Url {get;set;} @@ -43,7 +44,8 @@ class AddRemoveRunner [HttpPost] [AllowAnonymous] - public async Task Get() + [SwaggerResponse(200, type: typeof(GitHubAuthResult))] + public async Task Get([FromBody, Vss] AddRemoveRunner payload) { StringValues auth; if(!Request.Headers.TryGetValue("Authorization", out auth)) { @@ -52,7 +54,6 @@ public async Task Get() if(auth.FirstOrDefault()?.StartsWith("RemoteAuth ") != true || (RUNNER_TOKEN.Length > 0 && auth.First() != "RemoteAuth " + RUNNER_TOKEN) ) { return NotFound(); } - var payload = await FromBody(); var mySecurityKey = new RsaSecurityKey(Startup.AccessTokenParameter); var myIssuer = "http://githubactionsserver"; @@ -74,7 +75,7 @@ public async Task Get() var token = tokenHandler.CreateToken(tokenDescriptor); var payloadUrl = new Uri(payload.Url); var components = payloadUrl.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries); - return await Ok(new Runner.Server.Models.GitHubAuthResult() { + return await Ok(new GitHubAuthResult() { TenantUrl = new Uri(new Uri(ServerUrl), components.Length == 0 ? "runner/server" : components.Length > 1 ? $"{components[0]}/{components[1]}" : $"{components[0]}/server").ToString(), Token = tokenHandler.WriteToken(token), TokenSchema = "OAuthAccessToken" diff --git a/src/Runner.Server/Controllers/TaskController.cs b/src/Runner.Server/Controllers/TaskController.cs index 1430153abb0..792194cd37f 100644 --- a/src/Runner.Server/Controllers/TaskController.cs +++ b/src/Runner.Server/Controllers/TaskController.cs @@ -17,6 +17,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using Swashbuckle.AspNetCore.Annotations; namespace Runner.Server.Controllers { @@ -49,6 +50,7 @@ public IActionResult GetTask(Guid taskid) { [HttpGet("{taskid}/{version}")] [AllowAnonymous] + [SwaggerResponse(200, contentTypes: new[]{"application/octet-stream"})] public IActionResult GetTaskArchive(Guid taskid, string version) { var srunid = User.FindFirst("runid")?.Value; long runid = -1; diff --git a/src/Runner.Server/Controllers/TimeLineWebConsoleLogController.cs b/src/Runner.Server/Controllers/TimeLineWebConsoleLogController.cs index 26af693b545..30acd68089f 100644 --- a/src/Runner.Server/Controllers/TimeLineWebConsoleLogController.cs +++ b/src/Runner.Server/Controllers/TimeLineWebConsoleLogController.cs @@ -143,9 +143,8 @@ public static void AppendTimelineRecordFeed(TimelineRecordFeedLinesWrapper recor [HttpPost("{scopeIdentifier}/{hubName}/{planId}/{timelineId}/{recordId}")] [Authorize(AuthenticationSchemes = "Bearer", Policy = "AgentJob")] - public async Task AppendTimelineRecordFeed(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid recordId) + public IActionResult AppendTimelineRecordFeed(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid recordId, [FromBody, Vss] TimelineRecordFeedLinesWrapper record) { - var record = await FromBody(); // It seems the actions/runner sends faulty lines with linebreaks var regex = new Regex("\r?\n"); var nl = record.Value.SelectMany(lines => regex.Split(lines)).ToList(); diff --git a/src/Runner.Server/Controllers/TimelineController.cs b/src/Runner.Server/Controllers/TimelineController.cs index 9efd7e1b31d..3b0047833d9 100644 --- a/src/Runner.Server/Controllers/TimelineController.cs +++ b/src/Runner.Server/Controllers/TimelineController.cs @@ -17,6 +17,7 @@ using Microsoft.Extensions.Configuration; using GitHub.Actions.Pipelines.WebApi; using Newtonsoft.Json; +using Swashbuckle.AspNetCore.Annotations; namespace Runner.Server.Controllers { @@ -33,7 +34,7 @@ public TimelineController(SqLiteDb context, IConfiguration conf) : base(conf) _context = context; } - public void SyncLiveLogsToDb(Guid timelineId) { + internal void SyncLiveLogsToDb(Guid timelineId) { if(dict.TryGetValue(timelineId, out var entry)) { foreach(var rec in (from record in _context.TimeLineRecords where record.TimelineId == timelineId select record).Include(r => r.Log).ToList()) { if(rec.Log == null && entry.Item2.TryGetValue(rec.Id, out var value)) { @@ -68,12 +69,13 @@ public async Task GetTimelineRecords(Guid timelineId) { } [HttpPost("{scopeIdentifier}/{hubName}/{planId}/timeline")] - public async Task CreateTimeline() { - var timeline = await FromBody(); + [SwaggerResponse(200, type: typeof(Timeline))] + public async Task CreateTimeline([FromBody, Vss] Timeline timeline) { return await Ok(timeline); } [HttpGet("{scopeIdentifier}/{hubName}/{planId}/timeline/{timelineid}")] + [SwaggerResponse(200, type: typeof(Timeline))] public async Task GetTimeline(Guid timelineId) { var timeline = new Timeline(timelineId); var l = (from record in _context.TimeLineRecords where record.TimelineId == timelineId select record).Include(r => r.Log).ToList(); @@ -83,6 +85,7 @@ public async Task GetTimeline(Guid timelineId) { } [HttpPut("{scopeIdentifier}/{hubName}/{planId}/{timelineId}/attachments/{recordId}/{type}/{name}")] + [SwaggerResponse(200, type: typeof(TaskAttachment))] public async Task PutAttachment(Guid timelineId, Guid recordId, string type, string name) { var jobInfo = (from j in _context.Jobs where j.TimeLineId == timelineId select new { j.runid, j.Attempt }).FirstOrDefault(); var artifacts = new ArtifactController(_context, Configuration); @@ -172,13 +175,13 @@ private List MergeTimelineRecords(List timelineR [HttpPatch("{scopeIdentifier}/{hubName}/{planId}/{timelineId}")] [Authorize(AuthenticationSchemes = "Bearer", Policy = "AgentJob")] - public async Task Patch(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId) + [SwaggerResponse(200, type: typeof(VssJsonCollectionWrapper>))] + public async Task Patch(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, [FromBody, Vss] VssJsonCollectionWrapper> patch) { - var patch = await FromBody>>(); return await UpdateTimeLine(timelineId, patch, true); } - public async Task UpdateTimeLine(Guid timelineId, VssJsonCollectionWrapper> patch, bool outOfSyncTimeLineUpdate = false) + internal async Task UpdateTimeLine(Guid timelineId, VssJsonCollectionWrapper> patch, bool outOfSyncTimeLineUpdate = false) { var old = (from record in _context.TimeLineRecords where record.TimelineId == timelineId select record).Include(r => r.Log).ToList(); var records = old.ToList(); diff --git a/src/Runner.Server/Controllers/VssControllerBase.cs b/src/Runner.Server/Controllers/VssControllerBase.cs index 7f2018d1e39..3c0026186a0 100644 --- a/src/Runner.Server/Controllers/VssControllerBase.cs +++ b/src/Runner.Server/Controllers/VssControllerBase.cs @@ -41,13 +41,6 @@ protected VssControllerBase(IConfiguration configuration) { ServerUrl = configuration?.GetSection("Runner.Server")?.GetValue("ServerUrl"); } - protected async Task FromBody() { - using(var reader = new StreamReader(Request.Body)) { - string text = await reader.ReadToEndAsync(); - return JsonConvert.DeserializeObject(text); - } - } - protected async Task> FromBody2(HashAlgorithm hash = null, string expected = null) { var stream = Request.Body; if(hash != null && expected != null) { diff --git a/src/Runner.Server/Extensions.cs b/src/Runner.Server/Extensions.cs index 0aa5e0816cb..dc5b19beeed 100644 --- a/src/Runner.Server/Extensions.cs +++ b/src/Runner.Server/Extensions.cs @@ -1,9 +1,16 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; using GitHub.DistributedTask.ObjectTemplating.Tokens; using GitHub.DistributedTask.Pipelines.ContextData; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Newtonsoft.Json; -namespace Runner.Server { +namespace Runner.Server +{ public static class Extension { public static TemplateToken AssertPermissionsValues(this TemplateToken token, string objectDescription) { @@ -106,4 +113,30 @@ public static IDictionary Merge(this IDictionary // } // } } + + public class VssBinder : IModelBinder + { + public async Task BindModelAsync(ModelBindingContext bindingContext) + { + if (!bindingContext.HttpContext.Request.HasJsonContentType()) + { + throw new BadHttpRequestException( + "Request content type was not a recognized JSON content type.", + StatusCodes.Status415UnsupportedMediaType); + } + + using var sr = new StreamReader(bindingContext.HttpContext.Request.Body); + var str = await sr.ReadToEndAsync(); + + var valueType = bindingContext.ModelType; + var obj = JsonConvert.DeserializeObject(str, valueType); + bindingContext.Result = ModelBindingResult.Success(obj); + } + } + + public class VssAttribute : ModelBinderAttribute { + public VssAttribute() : base(typeof(VssBinder)) { + + } + } } \ No newline at end of file diff --git a/src/Runner.Server/Runner.Server.csproj b/src/Runner.Server/Runner.Server.csproj index 0e9f14b8682..c806a14e62e 100644 --- a/src/Runner.Server/Runner.Server.csproj +++ b/src/Runner.Server/Runner.Server.csproj @@ -13,6 +13,7 @@ + @@ -25,6 +26,8 @@ + + diff --git a/src/Runner.Server/Startup.cs b/src/Runner.Server/Startup.cs index 143e47e292c..764811d7ec3 100644 --- a/src/Runner.Server/Startup.cs +++ b/src/Runner.Server/Startup.cs @@ -40,6 +40,9 @@ using Microsoft.AspNetCore.Rewrite; using System.Net; using Microsoft.Net.Http.Headers; +using Swashbuckle.AspNetCore.SwaggerGen; +using GitHub.Services.WebApi; +using Swashbuckle.AspNetCore.Newtonsoft; namespace Runner.Server { @@ -106,6 +109,7 @@ public void ConfigureServices(IServiceCollection services) Type = ReferenceType.SecurityScheme } }; + c.EnableAnnotations(enableAnnotationsForInheritance: true, enableAnnotationsForPolymorphism: true); c.AddSecurityDefinition(securityScheme.Reference.Id, securityScheme); c.AddSecurityRequirement(new OpenApiSecurityRequirement { @@ -113,6 +117,7 @@ public void ConfigureServices(IServiceCollection services) }); }); + services.Replace(ServiceDescriptor.Transient((s) => new NewtonsoftDataContractResolver(new VssJsonMediaTypeFormatter().SerializerSettings))); #if EF_MIGRATION // By default we use an InMemoryDatabase, which is incompatible with sqlite migrations var sqlitecon = "Data Source=Agents.db;";