21 changed files with 248 additions and 339 deletions
@ -0,0 +1,23 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -1,12 +0,0 @@
|
||||
{ |
||||
"profiles": { |
||||
"Jobs": { |
||||
"commandName": "Project", |
||||
"launchBrowser": false, |
||||
"environmentVariables": { |
||||
"ASPNETCORE_ENVIRONMENT": "Development" |
||||
}, |
||||
"applicationUrl": "http://localhost:4409/" |
||||
} |
||||
} |
||||
} |
||||
@ -1,74 +0,0 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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