21 changed files with 248 additions and 339 deletions
@ -0,0 +1,23 @@ |
|||||||
|
using System.Threading.Tasks; |
||||||
|
using Microsoft.Extensions.Logging; |
||||||
|
using Quartz; |
||||||
|
|
||||||
|
namespace Bit.Api.Jobs |
||||||
|
{ |
||||||
|
public class AliveJob : IJob |
||||||
|
{ |
||||||
|
private readonly ILogger<AliveJob> _logger; |
||||||
|
|
||||||
|
public AliveJob( |
||||||
|
ILogger<AliveJob> logger) |
||||||
|
{ |
||||||
|
_logger = logger; |
||||||
|
} |
||||||
|
|
||||||
|
public Task Execute(IJobExecutionContext context) |
||||||
|
{ |
||||||
|
_logger.LogInformation("It's alive!"); |
||||||
|
return Task.FromResult(0); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
using System; |
||||||
|
using Quartz; |
||||||
|
using Quartz.Spi; |
||||||
|
|
||||||
|
namespace Bit.Api.Jobs |
||||||
|
{ |
||||||
|
public class JobFactory : IJobFactory |
||||||
|
{ |
||||||
|
private readonly IServiceProvider _container; |
||||||
|
|
||||||
|
public JobFactory(IServiceProvider container) |
||||||
|
{ |
||||||
|
_container = container; |
||||||
|
} |
||||||
|
|
||||||
|
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) |
||||||
|
{ |
||||||
|
return _container.GetService(bundle.JobDetail.JobType) as IJob; |
||||||
|
} |
||||||
|
|
||||||
|
public void ReturnJob(IJob job) |
||||||
|
{ |
||||||
|
var disposable = job as IDisposable; |
||||||
|
disposable?.Dispose(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
using System; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using Microsoft.Extensions.Logging; |
||||||
|
using Quartz; |
||||||
|
|
||||||
|
namespace Bit.Api.Jobs |
||||||
|
{ |
||||||
|
public class JobListener : IJobListener |
||||||
|
{ |
||||||
|
private readonly ILogger<JobListener> _logger; |
||||||
|
|
||||||
|
public JobListener(ILogger<JobListener> logger) |
||||||
|
{ |
||||||
|
_logger = logger; |
||||||
|
} |
||||||
|
|
||||||
|
public string Name => "JobListener"; |
||||||
|
|
||||||
|
public Task JobExecutionVetoed(IJobExecutionContext context, |
||||||
|
CancellationToken cancellationToken = default(CancellationToken)) |
||||||
|
{ |
||||||
|
return Task.FromResult(0); |
||||||
|
} |
||||||
|
|
||||||
|
public Task JobToBeExecuted(IJobExecutionContext context, |
||||||
|
CancellationToken cancellationToken = default(CancellationToken)) |
||||||
|
{ |
||||||
|
_logger.LogInformation("Starting job {0} at {1}.", context.JobDetail.JobType.Name, DateTime.UtcNow); |
||||||
|
return Task.FromResult(0); |
||||||
|
} |
||||||
|
|
||||||
|
public Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException, |
||||||
|
CancellationToken cancellationToken = default(CancellationToken)) |
||||||
|
{ |
||||||
|
_logger.LogInformation("Finished job {0} at {1}.", context.JobDetail.JobType.Name, DateTime.UtcNow); |
||||||
|
return Task.FromResult(0); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,80 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Specialized; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using Microsoft.Extensions.DependencyInjection; |
||||||
|
using Microsoft.Extensions.Hosting; |
||||||
|
using Microsoft.Extensions.Logging; |
||||||
|
using Quartz; |
||||||
|
using Quartz.Impl; |
||||||
|
using Quartz.Impl.Matchers; |
||||||
|
|
||||||
|
namespace Bit.Api.Jobs |
||||||
|
{ |
||||||
|
public class JobsHostedService : IHostedService, IDisposable |
||||||
|
{ |
||||||
|
private readonly IServiceProvider _serviceProvider; |
||||||
|
private readonly ILogger _logger; |
||||||
|
private readonly ILogger<JobListener> _listenerLogger; |
||||||
|
private IScheduler _scheduler; |
||||||
|
|
||||||
|
public JobsHostedService( |
||||||
|
IServiceProvider serviceProvider, |
||||||
|
ILogger<JobsHostedService> logger, |
||||||
|
ILogger<JobListener> listenerLogger) |
||||||
|
{ |
||||||
|
_serviceProvider = serviceProvider; |
||||||
|
_logger = logger; |
||||||
|
_listenerLogger = listenerLogger; |
||||||
|
} |
||||||
|
|
||||||
|
public async Task StartAsync(CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
var factory = new StdSchedulerFactory(new NameValueCollection |
||||||
|
{ |
||||||
|
{ "quartz.serializer.type", "binary" } |
||||||
|
}); |
||||||
|
_scheduler = await factory.GetScheduler(cancellationToken); |
||||||
|
_scheduler.JobFactory = new JobFactory(_serviceProvider); |
||||||
|
_scheduler.ListenerManager.AddJobListener(new JobListener(_listenerLogger), |
||||||
|
GroupMatcher<JobKey>.AnyGroup()); |
||||||
|
await _scheduler.Start(cancellationToken); |
||||||
|
|
||||||
|
var aliveJob = JobBuilder.Create<AliveJob>().Build(); |
||||||
|
var validateUsersJob = JobBuilder.Create<ValidateUsersJob>().Build(); |
||||||
|
var validateOrganizationsJob = JobBuilder.Create<ValidateOrganizationsJob>().Build(); |
||||||
|
|
||||||
|
var everyTopOfTheHourTrigger = TriggerBuilder.Create() |
||||||
|
.StartNow() |
||||||
|
.WithCronSchedule("0 0 * * * ?") |
||||||
|
.Build(); |
||||||
|
var everyTopOfTheSixthHourTrigger = TriggerBuilder.Create() |
||||||
|
.StartNow() |
||||||
|
.WithCronSchedule("0 0 */6 * * ?") |
||||||
|
.Build(); |
||||||
|
var everyTwelfthHourAndThirtyMinutesTrigger = TriggerBuilder.Create() |
||||||
|
.StartNow() |
||||||
|
.WithCronSchedule("0 30 */12 * * ?") |
||||||
|
.Build(); |
||||||
|
|
||||||
|
await _scheduler.ScheduleJob(aliveJob, everyTopOfTheHourTrigger); |
||||||
|
await _scheduler.ScheduleJob(validateUsersJob, everyTopOfTheSixthHourTrigger); |
||||||
|
await _scheduler.ScheduleJob(validateOrganizationsJob, everyTwelfthHourAndThirtyMinutesTrigger); |
||||||
|
} |
||||||
|
|
||||||
|
public async Task StopAsync(CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
await _scheduler?.Shutdown(cancellationToken); |
||||||
|
} |
||||||
|
|
||||||
|
public void Dispose() |
||||||
|
{ } |
||||||
|
|
||||||
|
public static void AddJobsServices(IServiceCollection services) |
||||||
|
{ |
||||||
|
services.AddTransient<AliveJob>(); |
||||||
|
services.AddTransient<ValidateUsersJob>(); |
||||||
|
services.AddTransient<ValidateOrganizationsJob>(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,34 @@ |
|||||||
|
using System; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using Bit.Core.Services; |
||||||
|
using Microsoft.Extensions.Logging; |
||||||
|
using Quartz; |
||||||
|
|
||||||
|
namespace Bit.Api.Jobs |
||||||
|
{ |
||||||
|
public class ValidateOrganizationsJob : IJob |
||||||
|
{ |
||||||
|
private readonly ILicensingService _licensingService; |
||||||
|
private readonly ILogger<ValidateOrganizationsJob> _logger; |
||||||
|
|
||||||
|
public ValidateOrganizationsJob( |
||||||
|
ILicensingService licensingService, |
||||||
|
ILogger<ValidateOrganizationsJob> logger) |
||||||
|
{ |
||||||
|
_licensingService = licensingService; |
||||||
|
_logger = logger; |
||||||
|
} |
||||||
|
|
||||||
|
public async Task Execute(IJobExecutionContext context) |
||||||
|
{ |
||||||
|
try |
||||||
|
{ |
||||||
|
await _licensingService.ValidateOrganizationsAsync(); |
||||||
|
} |
||||||
|
catch(Exception e) |
||||||
|
{ |
||||||
|
_logger.LogError(2, e, "Error performing {0}.", nameof(ValidateOrganizationsJob)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,34 @@ |
|||||||
|
using System; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using Bit.Core.Services; |
||||||
|
using Microsoft.Extensions.Logging; |
||||||
|
using Quartz; |
||||||
|
|
||||||
|
namespace Bit.Api.Jobs |
||||||
|
{ |
||||||
|
public class ValidateUsersJob : IJob |
||||||
|
{ |
||||||
|
private readonly ILicensingService _licensingService; |
||||||
|
private readonly ILogger<ValidateUsersJob> _logger; |
||||||
|
|
||||||
|
public ValidateUsersJob( |
||||||
|
ILicensingService licensingService, |
||||||
|
ILogger<ValidateUsersJob> logger) |
||||||
|
{ |
||||||
|
_licensingService = licensingService; |
||||||
|
_logger = logger; |
||||||
|
} |
||||||
|
|
||||||
|
public async Task Execute(IJobExecutionContext context) |
||||||
|
{ |
||||||
|
try |
||||||
|
{ |
||||||
|
await _licensingService.ValidateUsersAsync(); |
||||||
|
} |
||||||
|
catch(Exception e) |
||||||
|
{ |
||||||
|
_logger.LogError(2, e, "Error performing {0}.", nameof(ValidateUsersJob)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -1,25 +0,0 @@ |
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web"> |
|
||||||
|
|
||||||
<PropertyGroup> |
|
||||||
<OutputType>Exe</OutputType> |
|
||||||
<TargetFramework>netcoreapp2.1</TargetFramework> |
|
||||||
<RootNamespace>Bit.Jobs</RootNamespace> |
|
||||||
<UserSecretsId>bitwarden-Jobs</UserSecretsId> |
|
||||||
</PropertyGroup> |
|
||||||
|
|
||||||
<ItemGroup> |
|
||||||
<Content Include="crontab"> |
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> |
|
||||||
</Content> |
|
||||||
</ItemGroup> |
|
||||||
|
|
||||||
<ItemGroup> |
|
||||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.2" /> |
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.1.1" /> |
|
||||||
</ItemGroup> |
|
||||||
|
|
||||||
<ItemGroup> |
|
||||||
<ProjectReference Include="..\Core\Core.csproj" /> |
|
||||||
</ItemGroup> |
|
||||||
|
|
||||||
</Project> |
|
||||||
@ -1,25 +0,0 @@ |
|||||||
using System.Threading; |
|
||||||
using System.Threading.Tasks; |
|
||||||
using Microsoft.AspNetCore.Hosting.Server; |
|
||||||
using Microsoft.AspNetCore.Http.Features; |
|
||||||
|
|
||||||
namespace Bit.Jobs |
|
||||||
{ |
|
||||||
public class NoopServer : IServer |
|
||||||
{ |
|
||||||
public IFeatureCollection Features => new FeatureCollection(); |
|
||||||
|
|
||||||
public void Dispose() |
|
||||||
{ } |
|
||||||
|
|
||||||
public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) |
|
||||||
{ |
|
||||||
return Task.FromResult(0); |
|
||||||
} |
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken) |
|
||||||
{ |
|
||||||
return Task.FromResult(0); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,97 +0,0 @@ |
|||||||
using Bit.Core.Services; |
|
||||||
using Microsoft.AspNetCore.Hosting; |
|
||||||
using Microsoft.Extensions.DependencyInjection; |
|
||||||
using Microsoft.Extensions.Logging; |
|
||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.IO; |
|
||||||
using System.Threading.Tasks; |
|
||||||
|
|
||||||
namespace Bit.Jobs |
|
||||||
{ |
|
||||||
public class Program |
|
||||||
{ |
|
||||||
private static ILicensingService _licensingService; |
|
||||||
private static ILogger<Program> _logger; |
|
||||||
|
|
||||||
public static void Main(string[] args) |
|
||||||
{ |
|
||||||
var parameters = ParseParameters(args); |
|
||||||
try |
|
||||||
{ |
|
||||||
var host = new WebHostBuilder() |
|
||||||
.UseContentRoot(parameters.ContainsKey("d") ? parameters["d"] : Directory.GetCurrentDirectory()) |
|
||||||
.UseStartup<Startup>() |
|
||||||
.UseServer(new NoopServer()) |
|
||||||
.Build(); |
|
||||||
|
|
||||||
_logger = host.Services.GetRequiredService<ILogger<Program>>(); |
|
||||||
_licensingService = host.Services.GetRequiredService<ILicensingService>(); |
|
||||||
} |
|
||||||
catch(Exception e) |
|
||||||
{ |
|
||||||
if(_logger != null) |
|
||||||
{ |
|
||||||
_logger.LogCritical(1, e, "Error while bootstrapping."); |
|
||||||
} |
|
||||||
throw e; |
|
||||||
} |
|
||||||
|
|
||||||
MainAsync(parameters).Wait(); |
|
||||||
} |
|
||||||
|
|
||||||
private async static Task MainAsync(IDictionary<string, string> parameters) |
|
||||||
{ |
|
||||||
if(!parameters.ContainsKey("j")) |
|
||||||
{ |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
_logger.LogInformation("Starting job {0}.", parameters["j"]); |
|
||||||
|
|
||||||
try |
|
||||||
{ |
|
||||||
switch(parameters["j"]) |
|
||||||
{ |
|
||||||
case "validate-organizations": |
|
||||||
await _licensingService.ValidateOrganizationsAsync(); |
|
||||||
break; |
|
||||||
case "validate-users-premium": |
|
||||||
await _licensingService.ValidateUsersAsync(); |
|
||||||
break; |
|
||||||
case "refresh-licenses": |
|
||||||
// TODO |
|
||||||
break; |
|
||||||
case "alive": |
|
||||||
_logger.LogInformation(DateTime.UtcNow.ToString()); |
|
||||||
break; |
|
||||||
default: |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
catch(Exception e) |
|
||||||
{ |
|
||||||
_logger.LogError(2, e, "Error performing job."); |
|
||||||
throw e; |
|
||||||
} |
|
||||||
|
|
||||||
_logger.LogInformation("Finished job {0}.", parameters["j"]); |
|
||||||
} |
|
||||||
|
|
||||||
private static IDictionary<string, string> ParseParameters(string[] args) |
|
||||||
{ |
|
||||||
var dict = new Dictionary<string, string>(); |
|
||||||
for(var i = 0; i < args.Length; i = i + 2) |
|
||||||
{ |
|
||||||
if(!args[i].StartsWith("-")) |
|
||||||
{ |
|
||||||
continue; |
|
||||||
} |
|
||||||
|
|
||||||
dict.Add(args[i].Substring(1), args[i + 1]); |
|
||||||
} |
|
||||||
|
|
||||||
return dict; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,12 +0,0 @@ |
|||||||
{ |
|
||||||
"profiles": { |
|
||||||
"Jobs": { |
|
||||||
"commandName": "Project", |
|
||||||
"launchBrowser": false, |
|
||||||
"environmentVariables": { |
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development" |
|
||||||
}, |
|
||||||
"applicationUrl": "http://localhost:4409/" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,74 +0,0 @@ |
|||||||
using System; |
|
||||||
using Microsoft.AspNetCore.Hosting; |
|
||||||
using Microsoft.Extensions.DependencyInjection; |
|
||||||
using Microsoft.Extensions.Configuration; |
|
||||||
using Bit.Core; |
|
||||||
using Bit.Core.Utilities; |
|
||||||
using Microsoft.Extensions.Logging; |
|
||||||
using Microsoft.AspNetCore.Builder; |
|
||||||
using Serilog.Events; |
|
||||||
|
|
||||||
namespace Bit.Jobs |
|
||||||
{ |
|
||||||
public class Startup |
|
||||||
{ |
|
||||||
public Startup(IHostingEnvironment env) |
|
||||||
{ |
|
||||||
var builder = new ConfigurationBuilder() |
|
||||||
.SetBasePath(env.ContentRootPath) |
|
||||||
.AddJsonFile("appsettings.json") |
|
||||||
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true); |
|
||||||
|
|
||||||
if(env.IsDevelopment()) |
|
||||||
{ |
|
||||||
builder.AddUserSecrets("bitwarden-Jobs"); |
|
||||||
} |
|
||||||
|
|
||||||
builder.AddEnvironmentVariables(); |
|
||||||
|
|
||||||
Configuration = builder.Build(); |
|
||||||
Environment = env; |
|
||||||
} |
|
||||||
|
|
||||||
public IConfigurationRoot Configuration { get; private set; } |
|
||||||
public IHostingEnvironment Environment { get; set; } |
|
||||||
|
|
||||||
public void ConfigureServices(IServiceCollection services) |
|
||||||
{ |
|
||||||
// Options |
|
||||||
services.AddOptions(); |
|
||||||
|
|
||||||
// Settings |
|
||||||
var globalSettings = services.AddGlobalSettingsServices(Configuration); |
|
||||||
|
|
||||||
// Data Protection |
|
||||||
services.AddCustomDataProtectionServices(Environment, globalSettings); |
|
||||||
|
|
||||||
// Repositories |
|
||||||
services.AddSqlServerRepositories(globalSettings); |
|
||||||
|
|
||||||
// Context |
|
||||||
services.AddScoped<CurrentContext>(); |
|
||||||
|
|
||||||
// Identity |
|
||||||
services.AddCustomIdentityServices(globalSettings); |
|
||||||
|
|
||||||
// Services |
|
||||||
services.AddBaseServices(); |
|
||||||
services.AddDefaultServices(globalSettings); |
|
||||||
} |
|
||||||
|
|
||||||
public void Configure( |
|
||||||
IApplicationBuilder app, |
|
||||||
IHostingEnvironment env, |
|
||||||
ILoggerFactory loggerFactory, |
|
||||||
IApplicationLifetime appLifetime, |
|
||||||
GlobalSettings globalSettings) |
|
||||||
{ |
|
||||||
loggerFactory |
|
||||||
.AddSerilog(app, env, appLifetime, globalSettings, e => e.Level >= LogEventLevel.Information) |
|
||||||
.AddConsole() |
|
||||||
.AddDebug(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,17 +0,0 @@ |
|||||||
{ |
|
||||||
"globalSettings": { |
|
||||||
"baseServiceUri": { |
|
||||||
"vault": "https://vault.bitwarden.com", |
|
||||||
"api": "https://api.bitwarden.com", |
|
||||||
"identity": "https://identity.bitwarden.com", |
|
||||||
"admin": "https://admin.bitwarden.com", |
|
||||||
"internalAdmin": "https://admin.bitwarden.com", |
|
||||||
"internalIdentity": "https://identity.bitwarden.com", |
|
||||||
"internalApi": "https://api.bitwarden.com", |
|
||||||
"internalVault": "https://vault.bitwarden.com" |
|
||||||
}, |
|
||||||
"braintree": { |
|
||||||
"production": true |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,58 +0,0 @@ |
|||||||
{ |
|
||||||
"globalSettings": { |
|
||||||
"selfHosted": false, |
|
||||||
"siteName": "Bitwarden", |
|
||||||
"projectName": "Jobs", |
|
||||||
"stripeApiKey": "SECRET", |
|
||||||
"baseServiceUri": { |
|
||||||
"vault": "https://localhost:8080", |
|
||||||
"api": "http://localhost:4000", |
|
||||||
"identity": "http://localhost:33656", |
|
||||||
"admin": "http://localhost:62911", |
|
||||||
"internalAdmin": "http://localhost:62911", |
|
||||||
"internalIdentity": "http://localhost:33656", |
|
||||||
"internalApi": "http://localhost:4000", |
|
||||||
"internalVault": "http://localhost:4001" |
|
||||||
}, |
|
||||||
"sqlServer": { |
|
||||||
"connectionString": "SECRET" |
|
||||||
}, |
|
||||||
"mail": { |
|
||||||
"sendGridApiKey": "SECRET", |
|
||||||
"replyToEmail": "hello@bitwarden.com" |
|
||||||
}, |
|
||||||
"identityServer": { |
|
||||||
"certificateThumbprint": "SECRET" |
|
||||||
}, |
|
||||||
"dataProtection": { |
|
||||||
"certificateThumbprint": "SECRET" |
|
||||||
}, |
|
||||||
"storage": { |
|
||||||
"connectionString": "SECRET" |
|
||||||
}, |
|
||||||
"documentDb": { |
|
||||||
"uri": "SECRET", |
|
||||||
"key": "SECRET" |
|
||||||
}, |
|
||||||
"sentry": { |
|
||||||
"dsn": "SECRET" |
|
||||||
}, |
|
||||||
"notificationHub": { |
|
||||||
"connectionString": "SECRET", |
|
||||||
"hubName": "SECRET" |
|
||||||
}, |
|
||||||
"yubico": { |
|
||||||
"clientid": "SECRET", |
|
||||||
"key": "SECRET" |
|
||||||
}, |
|
||||||
"duo": { |
|
||||||
"aKey": "SECRET" |
|
||||||
}, |
|
||||||
"braintree": { |
|
||||||
"production": false, |
|
||||||
"merchantId": "SECRET", |
|
||||||
"publicKey": "SECRET", |
|
||||||
"privateKey": "SECRET" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,5 +0,0 @@ |
|||||||
0 * * * * bitwarden dotnet /jobs/Jobs.dll -d /jobs -j alive >> /var/log/cron.log 2>&1 |
|
||||||
0 */6 * * * bitwarden dotnet /jobs/Jobs.dll -d /jobs -j validate-organizations >> /var/log/cron.log 2>&1 |
|
||||||
30 */12 * * * bitwarden dotnet /jobs/Jobs.dll -d /jobs -j validate-users-premium >> /var/log/cron.log 2>&1 |
|
||||||
|
|
||||||
# An empty line is required at the end of this file for a valid cron file. |
|
||||||
Loading…
Reference in new issue