Skip to content

Commit

Permalink
Use controllers for management endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
mburumaxwell committed Sep 19, 2023
1 parent b93ecb2 commit 2f00996
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ private async Task TestAsync(Func<InMemoryTestHarness, HttpClient, Task> execute
.ConfigureServices((context, services) =>
{
services.AddControllers()
.AddApplicationPart(typeof(WebhooksController).Assembly)
.AddApplicationPart(typeof(MainDbContext).Assembly)
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.AllowTrailingCommas = true;
Expand Down
120 changes: 120 additions & 0 deletions server/Tingle.Dependabot/Controllers/ManagementController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using Tingle.Dependabot.Events;
using Tingle.Dependabot.Models;
using Tingle.Dependabot.Models.Management;
using Tingle.Dependabot.Workflow;
using Tingle.EventBus;

namespace Tingle.Dependabot.Controllers;

[ApiController]
[Route("/mgnt")]
[Authorize(AuthConstants.PolicyNameManagement)]
public class ManagementController : ControllerBase // TODO: unit test this
{
private readonly MainDbContext dbContext;
private readonly IEventPublisher publisher;
private readonly AzureDevOpsProvider adoProvider;

public ManagementController(MainDbContext dbContext, IEventPublisher publisher, AzureDevOpsProvider adoProvider)
{
this.dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
this.publisher = publisher ?? throw new ArgumentNullException(nameof(publisher));
this.adoProvider = adoProvider ?? throw new ArgumentNullException(nameof(adoProvider));
}

[HttpPost("sync")]
public async Task<IActionResult> SyncAsync([FromBody] SynchronizationRequest model)
{
// request synchronization of the project
var evt = new ProcessSynchronization(model.Trigger);
await publisher.PublishAsync(evt);

return Ok();
}

[HttpPost("/webhooks/register")]
public async Task<IActionResult> WebhooksRegisterAsync()
{
await adoProvider.CreateOrUpdateSubscriptionsAsync();
return Ok();
}

[HttpGet("repos")]
public async Task<IActionResult> GetReposAsync()
{
var repos = await dbContext.Repositories.ToListAsync();
return Ok(repos);
}

[HttpGet("repos/{id}")]
public async Task<IActionResult> GetRepoAsync([FromRoute, Required] string id)
{
var repository = await dbContext.Repositories.SingleOrDefaultAsync(r => r.Id == id);
return Ok(repository);
}

[HttpGet("repos/{id}/jobs/{jobId}")]
public async Task<IActionResult> GetJobAsync([FromRoute, Required] string id, [FromRoute, Required] string jobId)
{
// ensure repository exists
var repository = await dbContext.Repositories.SingleOrDefaultAsync(r => r.Id == id);
if (repository is null)
{
return Problem(title: "repository_not_found", statusCode: 400);
}

// find the job
var job = dbContext.UpdateJobs.Where(j => j.RepositoryId == repository.Id && j.Id == jobId).SingleOrDefaultAsync();
return Ok(job);
}

[HttpPost("repos/{id}/sync")]
public async Task<IActionResult> SyncRepoAsync([FromRoute, Required] string id, [FromBody] SynchronizationRequest model)
{
// ensure repository exists
var repository = await dbContext.Repositories.SingleOrDefaultAsync(r => r.Id == id);
if (repository is null)
{
return Problem(title: "repository_not_found", statusCode: 400);
}

// request synchronization of the repository
var evt = new ProcessSynchronization(model.Trigger, repositoryId: repository.Id, null);
await publisher.PublishAsync(evt);

return Ok(repository);
}

[HttpPost("repos/{id}/trigger")]
public async Task<IActionResult> TriggerAsync([FromRoute, Required] string id, [FromBody] TriggerUpdateRequest model)
{
// ensure repository exists
var repository = await dbContext.Repositories.SingleOrDefaultAsync(r => r.Id == id);
if (repository is null)
{
return Problem(title: "repository_not_found", statusCode: 400);
}

// ensure the repository update exists
var update = repository.Updates.ElementAtOrDefault(model.Id!.Value);
if (update is null)
{
return Problem(title: "repository_update_not_found", statusCode: 400);
}

// trigger update for specific update
var evt = new TriggerUpdateJobsEvent
{
RepositoryId = repository.Id,
RepositoryUpdateId = model.Id.Value,
Trigger = UpdateJobTrigger.Manual,
};
await publisher.PublishAsync(evt);

return Ok(repository);
}
}
112 changes: 7 additions & 105 deletions server/Tingle.Dependabot/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,13 @@
builder.Services.AddDistributedMemoryCache();

// Configure other services
builder.Services.AddWorkflowServices(builder.Configuration.GetSection("Workflow"));
builder.Services.Configure<WorkflowOptions>(builder.Configuration.GetSection("Workflow"));
builder.Services.ConfigureOptions<WorkflowConfigureOptions>();
builder.Services.AddSingleton<UpdateRunner>();
builder.Services.AddSingleton<UpdateScheduler>();
builder.Services.AddScoped<AzureDevOpsProvider>();
builder.Services.AddScoped<Synchronizer>();
builder.Services.AddHostedService<WorkflowBackgroundService>();

// Add event bus
var selectedTransport = builder.Configuration.GetValue<EventBusTransportKind?>("EventBus:SelectedTransport");
Expand Down Expand Up @@ -116,114 +122,10 @@
app.MapHealthChecks("/health");
app.MapHealthChecks("/liveness", new HealthCheckOptions { Predicate = _ => false, });
app.MapControllers();
app.MapManagementApi();

// setup the application environment
await AppSetup.SetupAsync(app);

await app.RunAsync();

internal enum EventBusTransportKind { InMemory, ServiceBus, }

internal static class ApplicationExtensions
{
public static IServiceCollection AddWorkflowServices(this IServiceCollection services, IConfiguration configuration)
{
services.Configure<WorkflowOptions>(configuration);
services.ConfigureOptions<WorkflowConfigureOptions>();

services.AddSingleton<UpdateRunner>();
services.AddSingleton<UpdateScheduler>();

services.AddScoped<AzureDevOpsProvider>();
services.AddScoped<Synchronizer>();
services.AddHostedService<WorkflowBackgroundService>();

return services;
}

public static IEndpointRouteBuilder MapManagementApi(this IEndpointRouteBuilder builder)
{
var group = builder.MapGroup("");
group.RequireAuthorization(AuthConstants.PolicyNameManagement);

group.MapPost("/sync", async (IEventPublisher publisher, [FromBody] SynchronizationRequest model) =>
{
// request synchronization of the project
var evt = new ProcessSynchronization(model.Trigger);
await publisher.PublishAsync(evt);

return Results.Ok();
});

group.MapPost("/webhooks/register/azure", async (AzureDevOpsProvider adoProvider) =>
{
await adoProvider.CreateOrUpdateSubscriptionsAsync();
return Results.Ok();
});

group.MapGet("repos", async (MainDbContext dbContext) => Results.Ok(await dbContext.Repositories.ToListAsync()));
group.MapGet("repos/{id}", async (MainDbContext dbContext, [FromRoute, Required] string id) => Results.Ok(await dbContext.Repositories.SingleOrDefaultAsync(r => r.Id == id)));
group.MapPost("repos/{id}/sync", async (IEventPublisher publisher, MainDbContext dbContext, [FromRoute, Required] string id, [FromBody] SynchronizationRequest model) =>
{
if (!MiniValidator.TryValidate(model, out var errors)) return Results.ValidationProblem(errors);

// ensure repository exists
var repository = await dbContext.Repositories.SingleOrDefaultAsync(r => r.Id == id);
if (repository is null)
{
return Results.Problem(title: "repository_not_found", statusCode: 400);
}

// request synchronization of the repository
var evt = new ProcessSynchronization(model.Trigger, repositoryId: repository.Id, null);
await publisher.PublishAsync(evt);

return Results.Ok(repository);
});
group.MapGet("repos/{id}/jobs/{jobId}", async (MainDbContext dbContext, [FromRoute, Required] string id, [FromRoute, Required] string jobId) =>
{
// ensure repository exists
var repository = await dbContext.Repositories.SingleOrDefaultAsync(r => r.Id == id);
if (repository is null)
{
return Results.Problem(title: "repository_not_found", statusCode: 400);
}

// find the job
var job = dbContext.UpdateJobs.Where(j => j.RepositoryId == repository.Id && j.Id == jobId).SingleOrDefaultAsync();
return Results.Ok(job);
});
group.MapPost("repos/{id}/trigger", async (IEventPublisher publisher, MainDbContext dbContext, [FromRoute, Required] string id, [FromBody] TriggerUpdateRequest model) =>
{
if (!MiniValidator.TryValidate(model, out var errors)) return Results.ValidationProblem(errors);

// ensure repository exists
var repository = await dbContext.Repositories.SingleOrDefaultAsync(r => r.Id == id);
if (repository is null)
{
return Results.Problem(title: "repository_not_found", statusCode: 400);
}

// ensure the repository update exists
var update = repository.Updates.ElementAtOrDefault(model.Id!.Value);
if (update is null)
{
return Results.Problem(title: "repository_update_not_found", statusCode: 400);
}

// trigger update for specific update
var evt = new TriggerUpdateJobsEvent
{
RepositoryId = repository.Id,
RepositoryUpdateId = model.Id.Value,
Trigger = UpdateJobTrigger.Manual,
};
await publisher.PublishAsync(evt);

return Results.Ok(repository);
});

return builder;
}
}
2 changes: 1 addition & 1 deletion server/Tingle.Dependabot/Workflow/AzureDevOpsProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

namespace Tingle.Dependabot.Workflow;

internal class AzureDevOpsProvider
public class AzureDevOpsProvider
{
private static readonly (string, string)[] SubscriptionEventTypes =
{
Expand Down

0 comments on commit 2f00996

Please sign in to comment.