Skip to content

Commit 1ee1aa4

Browse files
committed
Refactor LinkIndexProvider into own project
1 parent aa713b3 commit 1ee1aa4

File tree

8 files changed

+132
-136
lines changed

8 files changed

+132
-136
lines changed

docs-builder.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assembler-config-validate",
9999
actions\assembler-config-validate\action.yml = actions\assembler-config-validate\action.yml
100100
EndProjectSection
101101
EndProject
102+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Documentation.LinkIndex", "src\Elastic.Documentation.LinkIndex\Elastic.Documentation.LinkIndex.csproj", "{FD1AC230-798B-4AB9-8CE6-A06264885DBC}"
103+
EndProject
102104
Global
103105
GlobalSection(SolutionConfigurationPlatforms) = preSolution
104106
Debug|Any CPU = Debug|Any CPU
@@ -160,6 +162,10 @@ Global
160162
{CD94F9E4-7FCD-4152-81F1-4288C6B75367}.Debug|Any CPU.Build.0 = Debug|Any CPU
161163
{CD94F9E4-7FCD-4152-81F1-4288C6B75367}.Release|Any CPU.ActiveCfg = Release|Any CPU
162164
{CD94F9E4-7FCD-4152-81F1-4288C6B75367}.Release|Any CPU.Build.0 = Release|Any CPU
165+
{FD1AC230-798B-4AB9-8CE6-A06264885DBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
166+
{FD1AC230-798B-4AB9-8CE6-A06264885DBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
167+
{FD1AC230-798B-4AB9-8CE6-A06264885DBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
168+
{FD1AC230-798B-4AB9-8CE6-A06264885DBC}.Release|Any CPU.Build.0 = Release|Any CPU
163169
EndGlobalSection
164170
GlobalSection(NestedProjects) = preSolution
165171
{4D198E25-C211-41DC-9E84-B15E89BD7048} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A}
@@ -184,5 +190,6 @@ Global
184190
{7D36DDDA-9E0B-4D2C-8033-5D62FF8B6166} = {059E787F-85C1-43BE-9DD6-CE319E106383}
185191
{FB1C1954-D8E2-4745-BA62-04DD82FB4792} = {245023D2-D3CA-47B9-831D-DAB91A2FFDC7}
186192
{E20FEEF9-1D1A-4CDA-A546-7FDC573BE399} = {245023D2-D3CA-47B9-831D-DAB91A2FFDC7}
193+
{FD1AC230-798B-4AB9-8CE6-A06264885DBC} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A}
187194
EndGlobalSection
188195
EndGlobal
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="AWSSDK.S3" />
11+
</ItemGroup>
12+
13+
<ItemGroup>
14+
<ProjectReference Include="..\Elastic.Documentation\Elastic.Documentation.csproj" />
15+
</ItemGroup>
16+
17+
</Project>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System.Net;
2+
using System.Runtime.CompilerServices;
3+
using Amazon.S3;
4+
using Amazon.S3.Model;
5+
using Elastic.Documentation.Links;
6+
using Microsoft.Extensions.Logging;
7+
8+
namespace Elastic.Documentation.LinkIndex;
9+
10+
public interface ILinkIndexProvider
11+
{
12+
Task<LinkReferenceRegistry> GetLinkIndex(Cancel cancellationToken = default);
13+
Task SaveLinkIndex(LinkReferenceRegistry registry, Cancel cancellationToken = default);
14+
Task<LinkReference> GetLinkReference(string key, Cancel cancellationToken = default);
15+
}
16+
17+
public class AwsS3LinkIndexProvider(IAmazonS3 s3Client, string bucketName, string registryKey) : ILinkIndexProvider
18+
{
19+
public async Task<LinkReferenceRegistry> GetLinkIndex(Cancel cancellationToken = default)
20+
{
21+
var getObjectRequest = new GetObjectRequest
22+
{
23+
BucketName = bucketName,
24+
Key = registryKey
25+
};
26+
var getObjectResponse = await s3Client.GetObjectAsync(getObjectRequest, cancellationToken);
27+
await using var stream = getObjectResponse.ResponseStream;
28+
var linkIndex = LinkReferenceRegistry.Deserialize(stream);
29+
return linkIndex with { ETag = getObjectResponse.ETag };
30+
}
31+
32+
public async Task SaveLinkIndex(LinkReferenceRegistry registry, Cancel cancellationToken = default)
33+
{
34+
if (registry.ETag == null)
35+
// The ETag should not be null if the LinkReferenceRegistry was retrieved from GetLinkIndex()
36+
throw new InvalidOperationException($"{nameof(LinkReferenceRegistry)}.{nameof(registry.ETag)} cannot be null");
37+
var json = LinkReferenceRegistry.Serialize(registry);
38+
var putObjectRequest = new PutObjectRequest
39+
{
40+
BucketName = bucketName,
41+
Key = registryKey,
42+
ContentBody = json,
43+
ContentType = "application/json",
44+
IfMatch = registry.ETag // Only update if the ETag matches. Meaning the object has not been changed in the meantime.
45+
};
46+
var putResponse = await s3Client.PutObjectAsync(putObjectRequest, cancellationToken);
47+
if (putResponse.HttpStatusCode != HttpStatusCode.OK)
48+
throw new Exception($"Unable to save {nameof(LinkReferenceRegistry)} to s3://{bucketName}/{registryKey}");
49+
}
50+
51+
public async Task<LinkReference> GetLinkReference(string key, Cancel cancellationToken)
52+
{
53+
var getObjectRequest = new GetObjectRequest
54+
{
55+
BucketName = bucketName,
56+
Key = key
57+
};
58+
var getObjectResponse = await s3Client.GetObjectAsync(getObjectRequest, cancellationToken);
59+
await using var stream = getObjectResponse.ResponseStream;
60+
return LinkReference.Deserialize(stream);
61+
}
62+
}

src/Elastic.Documentation/Links/LinkReferenceRegistry.cs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,40 @@ public record LinkReferenceRegistry
1414
[JsonPropertyName("repositories")]
1515
public required Dictionary<string, Dictionary<string, LinkRegistryEntry>> Repositories { get; init; }
1616

17+
[JsonIgnore]
18+
public required string? ETag { get; init; }
19+
20+
public LinkReferenceRegistry WithLinkRegistryEntry(LinkRegistryEntry entry)
21+
{
22+
var copiedRepositories = new Dictionary<string, Dictionary<string, LinkRegistryEntry>>(Repositories);
23+
var repository = entry.Repository;
24+
var branch = entry.Branch;
25+
// repository already exists in links.json
26+
if (copiedRepositories.TryGetValue(repository, out var existingRepositoryEntry))
27+
{
28+
// The branch already exists in the repository entry
29+
if (existingRepositoryEntry.TryGetValue(branch, out var existingBranchEntry))
30+
{
31+
if (entry.UpdatedAt > existingBranchEntry.UpdatedAt)
32+
existingRepositoryEntry[branch] = entry;
33+
}
34+
// branch does not exist in the repository entry
35+
else
36+
{
37+
existingRepositoryEntry[branch] = entry;
38+
}
39+
}
40+
// onboarding new repository
41+
else
42+
{
43+
copiedRepositories.Add(repository, new Dictionary<string, LinkRegistryEntry>
44+
{
45+
{ branch, entry }
46+
});
47+
}
48+
return this with { Repositories = copiedRepositories };
49+
}
50+
1751
public static LinkReferenceRegistry Deserialize(Stream json) =>
1852
JsonSerializer.Deserialize(json, SourceGenerationContext.Default.LinkReferenceRegistry)!;
1953

@@ -46,4 +80,3 @@ public record LinkRegistryEntry
4680
[JsonPropertyName("updated_at")]
4781
public DateTime UpdatedAt { get; init; } = DateTime.MinValue;
4882
}
49-

src/infra/docs-lambda-index-publisher/LinkIndexProvider.cs

Lines changed: 0 additions & 100 deletions
This file was deleted.

src/infra/docs-lambda-index-publisher/LinkReferenceProvider.cs

Lines changed: 0 additions & 27 deletions
This file was deleted.

src/infra/docs-lambda-index-publisher/Program.cs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Amazon.S3;
1212
using Amazon.S3.Util;
1313
using Elastic.Documentation.Lambda.LinkIndexUploader;
14+
using Elastic.Documentation.LinkIndex;
1415
using Elastic.Documentation.Links;
1516

1617
const string bucketName = "elastic-docs-link-index";
@@ -26,19 +27,22 @@
2627
static async Task<SQSBatchResponse> Handler(SQSEvent ev, ILambdaContext context)
2728
{
2829
var s3Client = new AmazonS3Client();
29-
var linkIndexProvider = new LinkIndexProvider(s3Client, context.Logger, bucketName, indexFile);
30+
ILinkIndexProvider linkIndexProvider = new AwsS3LinkIndexProvider(s3Client, bucketName, indexFile);
3031
var batchItemFailures = new List<SQSBatchResponse.BatchItemFailure>();
32+
33+
var linkRegistry = await linkIndexProvider.GetLinkIndex();
34+
3135
foreach (var message in ev.Records)
3236
{
3337
context.Logger.LogInformation("Processing message {MessageId}", message.MessageId);
3438
context.Logger.LogInformation("Message body: {MessageBody}", message.Body);
3539
try
3640
{
37-
var s3RecordLinkReferenceTuples = await GetS3RecordLinkReferenceTuples(s3Client, message, context);
41+
var s3RecordLinkReferenceTuples = await GetS3RecordLinkReferenceTuples(linkIndexProvider, message);
3842
foreach (var (s3Record, linkReference) in s3RecordLinkReferenceTuples)
3943
{
4044
var newEntry = ConvertToLinkIndexEntry(s3Record, linkReference);
41-
await linkIndexProvider.UpdateLinkIndexEntry(newEntry);
45+
linkRegistry = linkRegistry.WithLinkRegistryEntry(newEntry);
4246
}
4347
}
4448
catch (Exception e)
@@ -53,7 +57,7 @@ static async Task<SQSBatchResponse> Handler(SQSEvent ev, ILambdaContext context)
5357
}
5458
try
5559
{
56-
await linkIndexProvider.Save();
60+
await linkIndexProvider.SaveLinkIndex(linkRegistry);
5761
var response = new SQSBatchResponse(batchItemFailures);
5862
if (batchItemFailures.Count > 0)
5963
context.Logger.LogInformation("Failed to process {batchItemFailuresCount} of {allMessagesCount} messages. Returning them to the queue.", batchItemFailures.Count, ev.Records.Count);
@@ -94,15 +98,14 @@ static LinkRegistryEntry ConvertToLinkIndexEntry(S3EventNotification.S3EventNoti
9498
};
9599
}
96100

97-
static async Task<IReadOnlyCollection<(S3EventNotification.S3EventNotificationRecord, LinkReference)>> GetS3RecordLinkReferenceTuples(IAmazonS3 s3Client,
98-
SQSEvent.SQSMessage message, ILambdaContext context)
101+
static async Task<IReadOnlyCollection<(S3EventNotification.S3EventNotificationRecord, LinkReference)>> GetS3RecordLinkReferenceTuples(ILinkIndexProvider linkIndexRepository,
102+
SQSEvent.SQSMessage message)
99103
{
100104
var s3Event = S3EventNotification.ParseJson(message.Body);
101105
var recordLinkReferenceTuples = new ConcurrentBag<(S3EventNotification.S3EventNotificationRecord, LinkReference)>();
102-
var linkReferenceProvider = new LinkReferenceProvider(s3Client, context.Logger, bucketName);
103106
await Parallel.ForEachAsync(s3Event.Records, async (record, ctx) =>
104107
{
105-
var linkReference = await linkReferenceProvider.GetLinkReference(record.S3.Object.Key, ctx);
108+
var linkReference = await linkIndexRepository.GetLinkReference(record.S3.Object.Key, ctx);
106109
recordLinkReferenceTuples.Add((record, linkReference));
107110
});
108111
return recordLinkReferenceTuples;

src/infra/docs-lambda-index-publisher/docs-lambda-index-publisher.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<PackageReference Include="AWSSDK.SQS" />
3030
</ItemGroup>
3131
<ItemGroup>
32+
<ProjectReference Include="..\..\Elastic.Documentation.LinkIndex\Elastic.Documentation.LinkIndex.csproj" />
3233
<ProjectReference Include="..\..\Elastic.Documentation\Elastic.Documentation.csproj"/>
3334
</ItemGroup>
3435
</Project>

0 commit comments

Comments
 (0)