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

Commit 9412e93

Browse files
author
Christy Henriksson
authored
Cleanup job for temp keys (#120)
1 parent 2719026 commit 9412e93

17 files changed

Lines changed: 489 additions & 2 deletions

NuGet.Jobs.sln

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio 15
4-
VisualStudioVersion = 15.0.26221.2
4+
VisualStudioVersion = 15.0.26228.0
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NuGet.Jobs.Common", "src\NuGet.Jobs.Common\NuGet.Jobs.Common.csproj", "{4B4B1EFB-8F33-42E6-B79F-54E7F3293D31}"
77
EndProject
@@ -75,6 +75,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SupportRequests", "SupportR
7575
EndProject
7676
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NuGet.SupportRequests.Notifications", "src\NuGet.SupportRequests.Notifications\NuGet.SupportRequests.Notifications.csproj", "{12719498-B87E-4E92-8C2B-30046393CF85}"
7777
EndProject
78+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gallery.Maintenance", "src\Gallery.Maintenance\Gallery.Maintenance.csproj", "{EFF021CA-1BF4-4C09-BFB8-D314EAAD24D2}"
79+
EndProject
7880
Global
7981
GlobalSection(SolutionConfigurationPlatforms) = preSolution
8082
Debug|Any CPU = Debug|Any CPU
@@ -175,6 +177,10 @@ Global
175177
{12719498-B87E-4E92-8C2B-30046393CF85}.Debug|Any CPU.Build.0 = Debug|Any CPU
176178
{12719498-B87E-4E92-8C2B-30046393CF85}.Release|Any CPU.ActiveCfg = Release|Any CPU
177179
{12719498-B87E-4E92-8C2B-30046393CF85}.Release|Any CPU.Build.0 = Release|Any CPU
180+
{EFF021CA-1BF4-4C09-BFB8-D314EAAD24D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
181+
{EFF021CA-1BF4-4C09-BFB8-D314EAAD24D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
182+
{EFF021CA-1BF4-4C09-BFB8-D314EAAD24D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
183+
{EFF021CA-1BF4-4C09-BFB8-D314EAAD24D2}.Release|Any CPU.Build.0 = Release|Any CPU
178184
EndGlobalSection
179185
GlobalSection(SolutionProperties) = preSolution
180186
HideSolutionNode = FALSE
@@ -203,5 +209,6 @@ Global
203209
{1EB7FF94-9B4A-4008-8F8E-5F867C0B00DE} = {678D7B14-F8BC-4193-99AF-2EE8AA390A02}
204210
{FA8C7905-985F-4919-AAA9-4B9A252F4977} = {88725659-D5F8-49F9-9B7E-D87C5B9917D7}
205211
{12719498-B87E-4E92-8C2B-30046393CF85} = {BEC3DF4D-9A04-42C8-8B4F-D42750202B4D}
212+
{EFF021CA-1BF4-4C09-BFB8-D314EAAD24D2} = {88725659-D5F8-49F9-9B7E-D87C5B9917D7}
206213
EndGlobalSection
207214
EndGlobal

src/Gallery.Maintenance/App.config

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<configuration>
3+
<startup>
4+
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
5+
</startup>
6+
<runtime>
7+
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
8+
<dependentAssembly>
9+
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
10+
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
11+
</dependentAssembly>
12+
<dependentAssembly>
13+
<assemblyIdentity name="Microsoft.Data.Services.Client" publicKeyToken="31bf3856ad364e35" culture="neutral" />
14+
<bindingRedirect oldVersion="0.0.0.0-5.7.0.0" newVersion="5.7.0.0" />
15+
</dependentAssembly>
16+
<dependentAssembly>
17+
<assemblyIdentity name="Microsoft.Data.OData" publicKeyToken="31bf3856ad364e35" culture="neutral" />
18+
<bindingRedirect oldVersion="0.0.0.0-5.7.0.0" newVersion="5.7.0.0" />
19+
</dependentAssembly>
20+
<dependentAssembly>
21+
<assemblyIdentity name="Microsoft.Data.Edm" publicKeyToken="31bf3856ad364e35" culture="neutral" />
22+
<bindingRedirect oldVersion="0.0.0.0-5.7.0.0" newVersion="5.7.0.0" />
23+
</dependentAssembly>
24+
<dependentAssembly>
25+
<assemblyIdentity name="Microsoft.ApplicationInsights" publicKeyToken="31bf3856ad364e35" culture="neutral" />
26+
<bindingRedirect oldVersion="0.0.0.0-2.1.0.0" newVersion="2.1.0.0" />
27+
</dependentAssembly>
28+
</assemblyBinding>
29+
</runtime>
30+
</configuration>
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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+
using System.Collections.Generic;
5+
using System.Data.SqlClient;
6+
using System.Linq;
7+
using System.Threading.Tasks;
8+
using Gallery.Maintenance.Models;
9+
using Microsoft.Extensions.Logging;
10+
11+
namespace Gallery.Maintenance
12+
{
13+
internal class DeleteExpiredVerificationKeysTask : IMaintenanceTask
14+
{
15+
private const int DefaultCommandTimeoutInSeconds = 300;
16+
17+
private const string SelectQuery = @"
18+
SELECT s.[CredentialKey], c.[UserKey], u.[Username], c.[Expires], s.[Subject] as ScopeSubject
19+
FROM [dbo].[Credentials] c
20+
INNER JOIN [dbo].[Scopes] s ON s.[CredentialKey] = c.[Key]
21+
INNER JOIN [dbo].[Users] u ON u.[Key] = c.[UserKey]
22+
WHERE c.[Type] LIKE 'apikey.verify%' AND c.[Expires] < GETUTCDATE()
23+
";
24+
25+
private const string DeleteQuery = @"
26+
DELETE FROM [dbo].[Scopes] WHERE [CredentialKey] IN ({0})
27+
DELETE FROM [dbo].[Credentials] WHERE [Key] IN ({0})";
28+
29+
public async Task<bool> RunAsync(Job job)
30+
{
31+
IEnumerable<PackageVerificationKey> expiredKeys;
32+
33+
using (var connection = await job.GalleryDatabase.ConnectTo())
34+
{
35+
expiredKeys = await connection.QueryWithRetryAsync<PackageVerificationKey>(
36+
SelectQuery,
37+
commandTimeout: DefaultCommandTimeoutInSeconds,
38+
maxRetries: 3);
39+
}
40+
41+
var credentialKeys = expiredKeys.Select(expiredKey =>
42+
{
43+
job.Logger.LogInformation(
44+
"Found expired verification key: Credential='{credentialKey}' UserKey='{userKey}', User='{userName}', Subject='{scopeSubject}', Expires={expires}",
45+
expiredKey.CredentialKey, expiredKey.UserKey, expiredKey.Username, expiredKey.ScopeSubject, expiredKey.Expires);
46+
47+
return expiredKey.CredentialKey;
48+
});
49+
50+
var rowCount = 0;
51+
var expectedRowCount = expiredKeys.Count() * 2; // credential and scope.
52+
53+
if (expectedRowCount > 0)
54+
{
55+
using (var connection = await job.GalleryDatabase.ConnectTo())
56+
{
57+
using (var transaction = connection.BeginTransaction())
58+
{
59+
rowCount = await connection.ExecuteAsync(
60+
string.Format(DeleteQuery, string.Join(",", credentialKeys)),
61+
transaction, DefaultCommandTimeoutInSeconds);
62+
63+
transaction.Commit();
64+
}
65+
}
66+
}
67+
68+
job.Logger.LogInformation("Deleted {0} expired verification keys and scopes. Expected={1}.", rowCount, expectedRowCount);
69+
70+
return rowCount == expectedRowCount;
71+
}
72+
}
73+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
4+
<PropertyGroup>
5+
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
6+
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
7+
<ProjectGuid>{EFF021CA-1BF4-4C09-BFB8-D314EAAD24D2}</ProjectGuid>
8+
<OutputType>Exe</OutputType>
9+
<RootNamespace>Gallery.Maintenance</RootNamespace>
10+
<AssemblyName>Gallery.Maintenance</AssemblyName>
11+
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
12+
<FileAlignment>512</FileAlignment>
13+
</PropertyGroup>
14+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
15+
<PlatformTarget>AnyCPU</PlatformTarget>
16+
<DebugSymbols>true</DebugSymbols>
17+
<DebugType>full</DebugType>
18+
<Optimize>false</Optimize>
19+
<OutputPath>bin\Debug\</OutputPath>
20+
<DefineConstants>DEBUG;TRACE</DefineConstants>
21+
<ErrorReport>prompt</ErrorReport>
22+
<WarningLevel>4</WarningLevel>
23+
</PropertyGroup>
24+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
25+
<PlatformTarget>AnyCPU</PlatformTarget>
26+
<DebugType>pdbonly</DebugType>
27+
<Optimize>true</Optimize>
28+
<OutputPath>bin\Release\</OutputPath>
29+
<DefineConstants>TRACE</DefineConstants>
30+
<ErrorReport>prompt</ErrorReport>
31+
<WarningLevel>4</WarningLevel>
32+
</PropertyGroup>
33+
<ItemGroup>
34+
<Reference Include="Microsoft.ApplicationInsights, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
35+
<HintPath>..\..\packages\Microsoft.ApplicationInsights.2.1.0\lib\net45\Microsoft.ApplicationInsights.dll</HintPath>
36+
</Reference>
37+
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
38+
<HintPath>..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.0.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
39+
</Reference>
40+
<Reference Include="Microsoft.Extensions.Logging, Version=1.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
41+
<HintPath>..\..\packages\Microsoft.Extensions.Logging.1.0.0\lib\netstandard1.1\Microsoft.Extensions.Logging.dll</HintPath>
42+
</Reference>
43+
<Reference Include="Microsoft.Extensions.Logging.Abstractions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
44+
<HintPath>..\..\packages\Microsoft.Extensions.Logging.Abstractions.1.0.0\lib\netstandard1.1\Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
45+
</Reference>
46+
<Reference Include="NuGet.Services.Logging, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
47+
<HintPath>..\..\packages\NuGet.Services.Logging.2.1.0\lib\net452\NuGet.Services.Logging.dll</HintPath>
48+
</Reference>
49+
<Reference Include="Serilog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
50+
<HintPath>..\..\packages\Serilog.2.0.0\lib\net45\Serilog.dll</HintPath>
51+
</Reference>
52+
<Reference Include="Serilog.Enrichers.Environment, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
53+
<HintPath>..\..\packages\Serilog.Enrichers.Environment.2.1.0\lib\net45\Serilog.Enrichers.Environment.dll</HintPath>
54+
</Reference>
55+
<Reference Include="Serilog.Enrichers.Process, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
56+
<HintPath>..\..\packages\Serilog.Enrichers.Process.2.0.0\lib\net45\Serilog.Enrichers.Process.dll</HintPath>
57+
</Reference>
58+
<Reference Include="Serilog.Extensions.Logging, Version=1.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
59+
<HintPath>..\..\packages\Serilog.Extensions.Logging.1.2.0\lib\net45\Serilog.Extensions.Logging.dll</HintPath>
60+
</Reference>
61+
<Reference Include="Serilog.Sinks.ApplicationInsights, Version=2.2.1.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
62+
<HintPath>..\..\packages\Serilog.Sinks.ApplicationInsights.2.2.1\lib\net45\Serilog.Sinks.ApplicationInsights.dll</HintPath>
63+
</Reference>
64+
<Reference Include="Serilog.Sinks.ColoredConsole, Version=2.0.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
65+
<HintPath>..\..\packages\Serilog.Sinks.ColoredConsole.2.0.0\lib\net45\Serilog.Sinks.ColoredConsole.dll</HintPath>
66+
</Reference>
67+
<Reference Include="SerilogTraceListener, Version=2.0.0.0, Culture=neutral, PublicKeyToken=9398e41289d9b801, processorArchitecture=MSIL">
68+
<HintPath>..\..\packages\SerilogTraceListener.2.0.10027\lib\net45\SerilogTraceListener.dll</HintPath>
69+
</Reference>
70+
<Reference Include="System" />
71+
<Reference Include="System.Core" />
72+
<Reference Include="System.Xml.Linq" />
73+
<Reference Include="System.Data.DataSetExtensions" />
74+
<Reference Include="Microsoft.CSharp" />
75+
<Reference Include="System.Data" />
76+
<Reference Include="System.Net.Http" />
77+
<Reference Include="System.Xml" />
78+
</ItemGroup>
79+
<ItemGroup>
80+
<Compile Include="DeleteExpiredVerificationKeysTask.cs" />
81+
<Compile Include="IMaintenanceTask.cs" />
82+
<Compile Include="Job.cs" />
83+
<Compile Include="LogEvents.cs" />
84+
<Compile Include="Models\PackageVerificationKey.cs" />
85+
<Compile Include="Program.cs" />
86+
<Compile Include="Properties\AssemblyInfo.cs" />
87+
</ItemGroup>
88+
<ItemGroup>
89+
<None Include="App.config" />
90+
<None Include="project.json" />
91+
<None Include="Scripts\Functions.ps1" />
92+
<None Include="Scripts\Gallery.Maintenance.cmd" />
93+
<None Include="Scripts\PostDeploy.ps1" />
94+
<None Include="Scripts\PreDeploy.ps1" />
95+
</ItemGroup>
96+
<ItemGroup>
97+
<ProjectReference Include="..\NuGet.Jobs.Common\NuGet.Jobs.Common.csproj">
98+
<Project>{4b4b1efb-8f33-42e6-b79f-54e7f3293d31}</Project>
99+
<Name>NuGet.Jobs.Common</Name>
100+
</ProjectReference>
101+
</ItemGroup>
102+
<ItemGroup>
103+
<Content Include="Scripts\nssm.exe" />
104+
</ItemGroup>
105+
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
106+
<Import Project="..\..\build\sign.targets" Condition="Exists('..\..\build\sign.targets')" />
107+
</Project>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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+
using System.Threading.Tasks;
5+
6+
namespace Gallery.Maintenance
7+
{
8+
/// <summary>
9+
/// A task to be run as a part of Gallery maintenance. Makes SQL queries against the Gallery database.
10+
/// </summary>
11+
public interface IMaintenanceTask
12+
{
13+
/// <summary>
14+
/// Run the maintenance task for the Gallery.
15+
/// </summary>
16+
/// <param name="job">Gallery maintenance job, for SQL connection and logging.</param>
17+
/// <returns>True for success, false for exception or other failure.</returns>
18+
Task<bool> RunAsync(Job job);
19+
}
20+
}

src/Gallery.Maintenance/Job.cs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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+
using System;
5+
using System.Collections.Generic;
6+
using System.Data;
7+
using System.Data.SqlClient;
8+
using System.Linq;
9+
using System.Threading.Tasks;
10+
using Microsoft.Extensions.Logging;
11+
using NuGet.Jobs;
12+
using NuGet.Services.Logging;
13+
14+
namespace Gallery.Maintenance
15+
{
16+
/// <summary>
17+
/// Runs all <see cref="IMaintenanceTask"/>s against the Gallery database.
18+
/// </summary>
19+
public class Job : JobBase
20+
{
21+
private static readonly Lazy<IEnumerable<IMaintenanceTask>> _tasks = new Lazy<IEnumerable<IMaintenanceTask>>(GetMaintenanceTasks);
22+
23+
public SqlConnectionStringBuilder GalleryDatabase { get; private set; }
24+
25+
public ILogger Logger { get; private set; }
26+
27+
public override bool Init(IDictionary<string, string> jobArgsDictionary)
28+
{
29+
try
30+
{
31+
var instrumentationKey = JobConfigurationManager.TryGetArgument(jobArgsDictionary, JobArgumentNames.InstrumentationKey);
32+
ApplicationInsights.Initialize(instrumentationKey);
33+
34+
var loggerConfiguration = LoggingSetup.CreateDefaultLoggerConfiguration(ConsoleLogOnly);
35+
var loggerFactory = LoggingSetup.CreateLoggerFactory(loggerConfiguration);
36+
Logger = loggerFactory.CreateLogger<Job>();
37+
38+
var databaseConnectionString = JobConfigurationManager.GetArgument(jobArgsDictionary, JobArgumentNames.GalleryDatabase);
39+
GalleryDatabase = new SqlConnectionStringBuilder(databaseConnectionString);
40+
}
41+
catch (Exception exception)
42+
{
43+
Logger.LogCritical(LogEvents.JobInitFailed, exception, "Failed to initialize job!");
44+
45+
return false;
46+
}
47+
48+
return true;
49+
}
50+
51+
public override async Task<bool> Run()
52+
{
53+
var result = true;
54+
55+
foreach (var task in _tasks.Value)
56+
{
57+
var taskName = task.GetType().Name;
58+
59+
try
60+
{
61+
Logger.LogInformation("Running task '{taskName}'...", taskName);
62+
63+
if (await task.RunAsync(this))
64+
{
65+
Logger.LogInformation("Finished task '{taskName}'.", taskName);
66+
}
67+
else
68+
{
69+
Logger.LogWarning("Task '{taskName}' returned failure status.", taskName);
70+
result = false;
71+
}
72+
}
73+
catch (Exception exception)
74+
{
75+
Logger.LogCritical(LogEvents.JobRunFailed, exception, "Job run failed for task '{taskName}'.", taskName);
76+
result = false;
77+
}
78+
}
79+
80+
return result;
81+
}
82+
83+
private static IEnumerable<IMaintenanceTask> GetMaintenanceTasks()
84+
{
85+
var taskBaseType = typeof(IMaintenanceTask);
86+
87+
return taskBaseType.Assembly.GetTypes()
88+
.Where(type => type.IsClass && taskBaseType.IsAssignableFrom(type))
89+
.Select(type => (IMaintenanceTask)Activator.CreateInstance(type));
90+
}
91+
}
92+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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+
using Microsoft.Extensions.Logging;
5+
6+
namespace Gallery.Maintenance
7+
{
8+
public class LogEvents
9+
{
10+
public static EventId JobRunFailed = new EventId(650, "Job run failed");
11+
public static EventId JobInitFailed = new EventId(651, "Job initialization failed");
12+
}
13+
}

0 commit comments

Comments
 (0)