Skip to content
This repository was archived by the owner on Jul 30, 2024. It is now read-only.

Commit 91e3d1c

Browse files
author
Christy Henriksson
authored
Migrate Gallery.CredentialExpiration to JsonConfig (#517)
1 parent 640e3d8 commit 91e3d1c

12 files changed

Lines changed: 167 additions & 68 deletions

File tree

src/ArchivePackages/ArchivePackages.Job.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Dapper;
1212
using Microsoft.Extensions.Configuration;
1313
using Microsoft.Extensions.DependencyInjection;
14+
using Microsoft.Extensions.Options;
1415
using Microsoft.WindowsAzure.Storage;
1516
using Microsoft.WindowsAzure.Storage.Blob;
1617
using Newtonsoft.Json.Linq;
@@ -76,7 +77,7 @@ public override void Init(IServiceContainer serviceContainer, IDictionary<string
7677
{
7778
base.Init(serviceContainer, jobArgsDictionary);
7879

79-
Configuration = _serviceProvider.GetRequiredService<InitializationConfiguration>();
80+
Configuration = _serviceProvider.GetRequiredService<IOptionsSnapshot<InitializationConfiguration>>().Value;
8081

8182
GalleryDatabase = GetDatabaseRegistration<GalleryDbConfiguration>();
8283

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace Gallery.CredentialExpiration
5+
{
6+
public class InitializationConfiguration
7+
{
8+
public int AllowEmailResendAfterDays { get; set; }
9+
10+
public string ContainerName { get; set; }
11+
12+
public string DataStorageAccount { get; set; }
13+
14+
public string GalleryAccountUrl { get; set; }
15+
16+
public string GalleryBrand { get; set; }
17+
18+
public string MailFrom { get; set; }
19+
20+
public string SmtpUri { get; set; }
21+
22+
public int WarnDaysBeforeExpiration { get; set; }
23+
24+
public bool WhatIf { get; set; }
25+
}
26+
}

src/Gallery.CredentialExpiration/Gallery.CredentialExpiration.csproj

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
<Reference Include="System.Data" />
4242
</ItemGroup>
4343
<ItemGroup>
44+
<Compile Include="Configuration\InitializationConfiguration.cs" />
4445
<Compile Include="GalleryCredentialExpiration.cs" />
4546
<Compile Include="ICredentialExpirationExporter.cs" />
4647
<Compile Include="JobRunTimeCursor.cs" />
@@ -65,6 +66,9 @@
6566
<SubType>Designer</SubType>
6667
</None>
6768
<None Include="Scripts\*" />
69+
<None Include="Settings\dev.json" />
70+
<None Include="Settings\int.json" />
71+
<None Include="Settings\prod.json" />
6872
</ItemGroup>
6973
<ItemGroup>
7074
<ProjectReference Include="..\NuGet.Jobs.Common\NuGet.Jobs.Common.csproj">
@@ -92,9 +96,6 @@
9296
<PackageReference Include="Newtonsoft.Json">
9397
<Version>9.0.1</Version>
9498
</PackageReference>
95-
<PackageReference Include="NuGet.Services.Sql">
96-
<Version>2.27.0</Version>
97-
</PackageReference>
9899
<PackageReference Include="NuGet.Services.Storage">
99100
<Version>2.1.3</Version>
100101
</PackageReference>

src/Gallery.CredentialExpiration/Gallery.CredentialExpiration.nuspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,7 @@
1818
<file src="Scripts\PreDeploy.ps1" />
1919
<file src="Scripts\PostDeploy.ps1" />
2020
<file src="Scripts\nssm.exe" />
21+
22+
<file src="Settings\*.json" target="bin" />
2123
</files>
2224
</package>

src/Gallery.CredentialExpiration/GalleryCredentialExpiration.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,20 @@
66
using System.Data.SqlClient;
77
using System.Linq;
88
using System.Threading.Tasks;
9-
using NuGet.Services.Sql;
109
using Gallery.CredentialExpiration.Models;
10+
using NuGet.Jobs.Configuration;
1111

1212
namespace Gallery.CredentialExpiration
1313
{
1414
public class GalleryCredentialExpiration : ICredentialExpirationExporter
1515
{
16+
private readonly Job _job;
1617
private readonly CredentialExpirationJobMetadata _jobMetadata;
17-
private readonly ISqlConnectionFactory _galleryDatabase;
1818

19-
public GalleryCredentialExpiration(CredentialExpirationJobMetadata jobMetadata, ISqlConnectionFactory galleryDatabase)
19+
public GalleryCredentialExpiration(Job job, CredentialExpirationJobMetadata jobMetadata)
2020
{
21+
_job = job;
2122
_jobMetadata = jobMetadata;
22-
_galleryDatabase = galleryDatabase;
2323
}
2424

2525
/// <summary>
@@ -51,7 +51,7 @@ public async Task<List<ExpiredCredentialData>> GetCredentialsAsync(TimeSpan time
5151
var minNotificationDate = ConvertToString(GetMinNotificationDate());
5252

5353
// Connect to database
54-
using (var galleryConnection = await _galleryDatabase.CreateAsync())
54+
using (var galleryConnection = await _job.OpenSqlConnectionAsync<GalleryDbConfiguration>())
5555
{
5656
// Fetch credentials that expire in _warnDaysBeforeExpiration days
5757
// + the user's e-mail address
@@ -84,7 +84,10 @@ public List<ExpiredCredentialData> GetExpiringCredentials(List<ExpiredCredential
8484
{
8585
// Send email to the accounts that will have credentials expiring in the next _warnDaysBeforeExpiration days and did not have any warning email sent yet.
8686
// Avoid cases when the cursor is out of date and MaxProcessedCredentialsTime < JobRuntime
87-
var sendEmailsDateLeftBoundary = (_jobMetadata.JobCursor.MaxProcessedCredentialsTime > _jobMetadata.JobRunTime) ? _jobMetadata.JobCursor.MaxProcessedCredentialsTime : _jobMetadata.JobRunTime;
87+
var sendEmailsDateLeftBoundary = (_jobMetadata.JobCursor.MaxProcessedCredentialsTime > _jobMetadata.JobRunTime)
88+
? _jobMetadata.JobCursor.MaxProcessedCredentialsTime
89+
: _jobMetadata.JobRunTime;
90+
8891
return credentialSet.Where( x => x.Expires > sendEmailsDateLeftBoundary).ToList();
8992
}
9093

src/Gallery.CredentialExpiration/Job.cs

Lines changed: 49 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,94 +2,75 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5-
using System.Collections.Concurrent;
65
using System.Collections.Generic;
76
using System.ComponentModel.Design;
8-
using System.Data.SqlClient;
97
using System.Linq;
108
using System.Net;
119
using System.Net.Mail;
1210
using System.Threading;
1311
using System.Threading.Tasks;
12+
using Autofac;
1413
using Gallery.CredentialExpiration.Models;
14+
using Microsoft.Extensions.Configuration;
15+
using Microsoft.Extensions.DependencyInjection;
1516
using Microsoft.Extensions.Logging;
17+
using Microsoft.Extensions.Options;
1618
using Microsoft.WindowsAzure.Storage;
1719
using Newtonsoft.Json;
18-
using Newtonsoft.Json.Linq;
1920
using NuGet.Jobs;
20-
using NuGet.Services.KeyVault;
21-
using NuGet.Services.Sql;
2221
using NuGet.Services.Storage;
2322

2423
namespace Gallery.CredentialExpiration
2524
{
26-
public class Job : JobBase
25+
public class Job : JsonConfigurationJob
2726
{
2827
private readonly TimeSpan _defaultCommandTimeout = TimeSpan.FromMinutes(30);
2928

3029
private readonly string _cursorFile = "cursorv2.json";
3130

32-
private bool _whatIf = false;
31+
private InitializationConfiguration Configuration { get; set; }
3332

34-
private string _galleryBrand;
35-
private string _galleryAccountUrl;
33+
private Storage Storage { get; set; }
3634

37-
private ISqlConnectionFactory _galleryDatabase;
38-
39-
private string _mailFrom;
40-
private SmtpClient _smtpClient;
41-
42-
private int _warnDaysBeforeExpiration = 10;
43-
44-
private Storage _storage;
35+
private SmtpClient SmtpClient { get; set; }
4536

4637
public override void Init(IServiceContainer serviceContainer, IDictionary<string, string> jobArgsDictionary)
4738
{
48-
_whatIf = JobConfigurationManager.TryGetBoolArgument(jobArgsDictionary, JobArgumentNames.WhatIf);
49-
50-
var secretInjector = (ISecretInjector)serviceContainer.GetService(typeof(ISecretInjector));
51-
var databaseConnectionString = JobConfigurationManager.GetArgument(jobArgsDictionary, JobArgumentNames.GalleryDatabase);
52-
_galleryDatabase = new AzureSqlConnectionFactory(databaseConnectionString, secretInjector);
53-
54-
_galleryBrand = JobConfigurationManager.GetArgument(jobArgsDictionary, MyJobArgumentNames.GalleryBrand);
55-
_galleryAccountUrl = JobConfigurationManager.GetArgument(jobArgsDictionary, MyJobArgumentNames.GalleryAccountUrl);
39+
base.Init(serviceContainer, jobArgsDictionary);
5640

57-
_mailFrom = JobConfigurationManager.GetArgument(jobArgsDictionary, JobArgumentNames.MailFrom);
41+
Configuration = _serviceProvider.GetRequiredService<IOptionsSnapshot<InitializationConfiguration>>().Value;
5842

59-
var smtpConnectionString = JobConfigurationManager.GetArgument(jobArgsDictionary, JobArgumentNames.SmtpUri);
60-
var smtpUri = new SmtpUri(new Uri(smtpConnectionString));
61-
_smtpClient = CreateSmtpClient(smtpUri);
62-
63-
_warnDaysBeforeExpiration = JobConfigurationManager.TryGetIntArgument(jobArgsDictionary, MyJobArgumentNames.WarnDaysBeforeExpiration)
64-
?? _warnDaysBeforeExpiration;
65-
66-
var storageConnectionString = JobConfigurationManager.GetArgument(jobArgsDictionary, JobArgumentNames.DataStorageAccount);
67-
var storageContainerName = JobConfigurationManager.GetArgument(jobArgsDictionary, JobArgumentNames.ContainerName);
68-
69-
var storageAccount = CloudStorageAccount.Parse(storageConnectionString);
70-
var storageFactory = new AzureStorageFactory(storageAccount, storageContainerName, LoggerFactory);
71-
_storage = storageFactory.Create();
43+
SmtpClient = CreateSmtpClient(Configuration.SmtpUri);
44+
45+
var storageAccount = CloudStorageAccount.Parse(Configuration.DataStorageAccount);
46+
var storageFactory = new AzureStorageFactory(storageAccount, Configuration.ContainerName, LoggerFactory);
47+
Storage = storageFactory.Create();
7248
}
7349

7450
public override async Task Run()
7551
{
7652
var jobRunTime = DateTimeOffset.UtcNow;
7753
// Default values
7854
var jobCursor = new JobRunTimeCursor( jobCursorTime: jobRunTime, maxProcessedCredentialsTime: jobRunTime );
79-
var galleryCredentialExpiration = new GalleryCredentialExpiration(new CredentialExpirationJobMetadata(jobRunTime, _warnDaysBeforeExpiration, jobCursor), _galleryDatabase);
55+
var galleryCredentialExpiration = new GalleryCredentialExpiration(this,
56+
new CredentialExpirationJobMetadata(jobRunTime, Configuration.WarnDaysBeforeExpiration, jobCursor));
8057

8158
try
8259
{
8360
List<ExpiredCredentialData> credentialsInRange = null;
8461

8562
// Get the most recent date for the emails being sent
86-
if (_storage.Exists(_cursorFile))
63+
if (Storage.Exists(_cursorFile))
8764
{
88-
string content = await _storage.LoadString(_storage.ResolveUri(_cursorFile), CancellationToken.None);
65+
string content = await Storage.LoadString(Storage.ResolveUri(_cursorFile), CancellationToken.None);
8966
// Load from cursor
9067
// Throw if the schema is not correct to ensure that not-intended emails are sent.
91-
jobCursor = JsonConvert.DeserializeObject<JobRunTimeCursor>(content, new JsonSerializerSettings() { MissingMemberHandling = MissingMemberHandling.Error });
92-
galleryCredentialExpiration = new GalleryCredentialExpiration(new CredentialExpirationJobMetadata(jobRunTime, _warnDaysBeforeExpiration, jobCursor), _galleryDatabase);
68+
jobCursor = JsonConvert.DeserializeObject<JobRunTimeCursor>(
69+
content,
70+
new JsonSerializerSettings() { MissingMemberHandling = MissingMemberHandling.Error });
71+
72+
galleryCredentialExpiration = new GalleryCredentialExpiration(this,
73+
new CredentialExpirationJobMetadata(jobRunTime, Configuration.WarnDaysBeforeExpiration, jobCursor));
9374
}
9475

9576
// Connect to database
@@ -126,10 +107,13 @@ public override async Task Run()
126107
}
127108
finally
128109
{
129-
JobRunTimeCursor newCursor = new JobRunTimeCursor( jobCursorTime: jobRunTime, maxProcessedCredentialsTime: galleryCredentialExpiration.GetMaxNotificationDate());
110+
JobRunTimeCursor newCursor = new JobRunTimeCursor(
111+
jobCursorTime: jobRunTime,
112+
maxProcessedCredentialsTime: galleryCredentialExpiration.GetMaxNotificationDate());
113+
130114
string json = JsonConvert.SerializeObject(newCursor);
131115
var content = new StringStorageContent(json, "application/json");
132-
await _storage.Save(_storage.ResolveUri(_cursorFile), content, CancellationToken.None);
116+
await Storage.Save(Storage.ResolveUri(_cursorFile), content, CancellationToken.None);
133117
}
134118
}
135119

@@ -146,7 +130,7 @@ private async Task HandleExpiredCredentialEmail(string username, List<ExpiredCre
146130

147131
// Build message
148132
var userEmail = credentialList.FirstOrDefault().EmailAddress;
149-
var mailMessage = new MailMessage(_mailFrom, userEmail);
133+
var mailMessage = new MailMessage(Configuration.MailFrom, userEmail);
150134

151135
var apiKeyExpiryMessageList = credentialList
152136
.Select(x => BuildApiKeyExpiryMessage(x.Description, x.Expires, jobRunTime))
@@ -156,21 +140,21 @@ private async Task HandleExpiredCredentialEmail(string username, List<ExpiredCre
156140
// Build email body
157141
if (expired)
158142
{
159-
mailMessage.Subject = string.Format(Strings.ExpiredEmailSubject, _galleryBrand);
160-
mailMessage.Body = string.Format(Strings.ExpiredEmailBody, username, _galleryBrand, apiKeyExpiryMessage, _galleryAccountUrl);
143+
mailMessage.Subject = string.Format(Strings.ExpiredEmailSubject, Configuration.GalleryBrand);
144+
mailMessage.Body = string.Format(Strings.ExpiredEmailBody, username, Configuration.GalleryBrand, apiKeyExpiryMessage, Configuration.GalleryAccountUrl);
161145
}
162146
else
163147
{
164-
mailMessage.Subject = string.Format(Strings.ExpiringEmailSubject, _galleryBrand);
165-
mailMessage.Body = string.Format(Strings.ExpiringEmailBody, username, _galleryBrand, apiKeyExpiryMessage, _galleryAccountUrl);
148+
mailMessage.Subject = string.Format(Strings.ExpiringEmailSubject, Configuration.GalleryBrand);
149+
mailMessage.Body = string.Format(Strings.ExpiringEmailBody, username, Configuration.GalleryBrand, apiKeyExpiryMessage, Configuration.GalleryAccountUrl);
166150
}
167151

168152
// Send email
169153
try
170154
{
171-
if (!_whatIf) // if WhatIf is passed, we will not send e-mails (e.g. dev/int don't have to annoy users)
155+
if (!Configuration.WhatIf) // if WhatIf is passed, we will not send e-mails (e.g. dev/int don't have to annoy users)
172156
{
173-
await _smtpClient.SendMailAsync(mailMessage);
157+
await SmtpClient.SendMailAsync(mailMessage);
174158
}
175159

176160
Logger.LogInformation("Handled {Expired} credential .",
@@ -200,9 +184,10 @@ private static string BuildApiKeyExpiryMessage(string description, DateTimeOffse
200184
// \u2022 - Unicode for bullet point.
201185
return "\u2022 " + message + Environment.NewLine;
202186
}
203-
204-
private SmtpClient CreateSmtpClient(SmtpUri smtpUri)
187+
188+
private SmtpClient CreateSmtpClient(string smtpUriString)
205189
{
190+
var smtpUri = new SmtpUri(new Uri(smtpUriString));
206191
var smtpClient = new SmtpClient(smtpUri.Host, smtpUri.Port)
207192
{
208193
EnableSsl = smtpUri.Secure
@@ -218,5 +203,14 @@ private SmtpClient CreateSmtpClient(SmtpUri smtpUri)
218203

219204
return smtpClient;
220205
}
206+
207+
protected override void ConfigureAutofacServices(ContainerBuilder containerBuilder)
208+
{
209+
}
210+
211+
protected override void ConfigureJobServices(IServiceCollection services, IConfigurationRoot configurationRoot)
212+
{
213+
ConfigureInitializationSection<InitializationConfiguration>(services, configurationRoot);
214+
}
221215
}
222216
}

src/Gallery.CredentialExpiration/Scripts/Gallery.CredentialExpiration.cmd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ cd bin
99

1010
REM SmtpUri is expected to be of the format: smtps://username:password@host:port. Note that if username contains an "@", you need to URI encode it!
1111

12-
start /w gallery.credentialexpiration.exe -VaultName "#{Deployment.Azure.KeyVault.VaultName}" -ClientId "#{Deployment.Azure.KeyVault.ClientId}" -CertificateThumbprint "#{Deployment.Azure.KeyVault.CertificateThumbprint}" -WhatIf #{Jobs.gallery.credentialexpiration.WhatIf} -WarnDaysBeforeExpiration #{Jobs.gallery.credentialexpiration.WarnDaysBeforeExpiration} -MailFrom "#{Jobs.gallery.credentialexpiration.MailFrom}" -GalleryBrand "#{Jobs.gallery.credentialexpiration.GalleryBrand}" -GalleryAccountUrl "#{Jobs.gallery.credentialexpiration.GalleryAccountUrl}" -SmtpUri "#{Jobs.gallery.credentialexpiration.SmtpUri}" -GalleryDatabase "#{Jobs.gallery.credentialexpiration.GalleryDatabase}" -InstrumentationKey "#{Jobs.gallery.credentialexpiration.InstrumentationKey}" -verbose true -Interval #{Jobs.gallery.credentialexpiration.Interval} -DataStorageAccount "#{Jobs.gallery.credentialexpiration.Storage.Primary}" -ContainerName "#{Jobs.gallery.credentialexpiration.ContainerName}"
12+
start /w gallery.credentialexpiration.exe -Configuration "#{Jobs.gallery.credentialexpiration.Configuration}" -InstrumentationKey "#{Jobs.gallery.credentialexpiration.InstrumentationKey}" -verbose true -Interval #{Jobs.gallery.credentialexpiration.Interval}
1313

1414
echo "Finished #{Jobs.gallery.credentialexpiration.Title}"
1515

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"Initialization": {
3+
"AllowEmailResendAfterDays": 7,
4+
"ContainerName": "credentialexpiration",
5+
"DataStorageAccount": "DefaultEndpointsProtocol=https;AccountName=nugetdevuse2gallery;AccountKey=$$Dev-NuGetDevUse2Gallery-StorageKey$$",
6+
"GalleryAccountUrl": "https://dev.nugettest.org/account/ApiKeys",
7+
"GalleryBrand": "NuGet Gallery",
8+
"MailFrom": "[email protected]",
9+
"SmtpUri": "#{Jobs.gallery.credentialexpiration.SmtpUri}",
10+
"WarnDaysBeforeExpiration": 7,
11+
"WhatIf": true
12+
},
13+
14+
"GalleryDb": {
15+
"ConnectionString": "Data Source=tcp:#{Deployment.Azure.Sql.GalleryDatabaseAddress};Initial Catalog=nuget-dev-0-v2gallery;User ID=$$Dev-GalleryDBReadOnly-UserName$$;Password=$$Dev-GalleryDBReadOnly-Password$$;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
16+
},
17+
18+
"KeyVault_VaultName": "#{Deployment.Azure.KeyVault.VaultName}",
19+
"KeyVault_ClientId": "#{Deployment.Azure.KeyVault.ClientId}",
20+
"KeyVault_CertificateThumbprint": "#{Deployment.Azure.KeyVault.CertificateThumbprint}",
21+
"KeyVault_ValidateCertificate": true,
22+
"KeyVault_StoreName": "My",
23+
"KeyVault_StoreLocation": "LocalMachine"
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"Initialization": {
3+
"AllowEmailResendAfterDays": 7,
4+
"ContainerName": "credentialexpiration",
5+
"DataStorageAccount": "DefaultEndpointsProtocol=https;AccountName=nugetintusncgallery;AccountKey=$$Int-NuGetIntUsncGallery-StorageKey$$",
6+
"GalleryAccountUrl": "https://int.nugettest.org/account/ApiKeys",
7+
"GalleryBrand": "NuGet Gallery",
8+
"MailFrom": "[email protected]",
9+
"SmtpUri": "#{Jobs.gallery.credentialexpiration.SmtpUri}",
10+
"WarnDaysBeforeExpiration": 7,
11+
"WhatIf": true
12+
},
13+
14+
"GalleryDb": {
15+
"ConnectionString": "Data Source=tcp:#{Deployment.Azure.Sql.GalleryDatabaseAddress};Initial Catalog=nuget-int-0-v2gallery;User ID=$$Int-GalleryDBReadOnly-UserName$$;Password=$$Int-GalleryDBReadOnly-Password$$;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
16+
},
17+
18+
"KeyVault_VaultName": "#{Deployment.Azure.KeyVault.VaultName}",
19+
"KeyVault_ClientId": "#{Deployment.Azure.KeyVault.ClientId}",
20+
"KeyVault_CertificateThumbprint": "#{Deployment.Azure.KeyVault.CertificateThumbprint}",
21+
"KeyVault_ValidateCertificate": true,
22+
"KeyVault_StoreName": "My",
23+
"KeyVault_StoreLocation": "LocalMachine"
24+
}

0 commit comments

Comments
 (0)