-
Notifications
You must be signed in to change notification settings - Fork 20
Real redirects support #1397
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Real redirects support #1397
Changes from all commits
e2234fd
6a8201c
7760710
af7f0d0
8e694a0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,10 +3,12 @@ | |
// See the LICENSE file in the project root for more information | ||
|
||
using System.Collections.Frozen; | ||
using System.Text.Json; | ||
using Documentation.Assembler.Exporters; | ||
using Documentation.Assembler.Navigation; | ||
using Elastic.Documentation.Legacy; | ||
using Elastic.Documentation.Links; | ||
using Elastic.Documentation.Serialization; | ||
using Elastic.Markdown; | ||
using Elastic.Markdown.Exporters; | ||
using Elastic.Markdown.Links.CrossLinks; | ||
|
@@ -85,6 +87,8 @@ public async Task BuildAllAsync(FrozenDictionary<string, AssemblerDocumentationS | |
} | ||
} | ||
|
||
await OutputRedirectsAsync(redirects, ctx); | ||
|
||
tasks = markdownExporters.Select(async e => await e.StopAsync(ctx)); | ||
await Task.WhenAll(tasks); | ||
} | ||
|
@@ -143,4 +147,16 @@ private void SetFeatureFlags(AssemblerDocumentationSet set) | |
set.DocumentationSet.Configuration.Features.Set(configurationFeatureFlag.Key, configurationFeatureFlag.Value); | ||
} | ||
} | ||
|
||
private async Task OutputRedirectsAsync(Dictionary<string, string> redirects, Cancel ctx) | ||
{ | ||
var uniqueRedirects = redirects | ||
.Where(x => !x.Key.TrimEnd('/').Equals(x.Value.TrimEnd('/'))) | ||
.ToFrozenDictionary(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That way we can also rely on the regular dictionary serialization. |
||
var redirectsFile = context.WriteFileSystem.FileInfo.New(Path.Combine(context.OutputDirectory.FullName, "redirects.json")); | ||
_logger.LogInformation("Writing {Count} resolved redirects to {Path}", uniqueRedirects.Count, redirectsFile.FullName); | ||
|
||
var redirectsJson = JsonSerializer.Serialize(uniqueRedirects, SourceGenerationContext.Default.FrozenDictionaryStringString); | ||
await context.WriteFileSystem.File.WriteAllTextAsync(redirectsFile.FullName, redirectsJson, ctx); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,13 +4,20 @@ | |
|
||
using System.Diagnostics.CodeAnalysis; | ||
using System.IO.Abstractions; | ||
using System.Text.Json; | ||
using Actions.Core.Services; | ||
using Amazon.CloudFront; | ||
using Amazon.CloudFrontKeyValueStore; | ||
using Amazon.CloudFrontKeyValueStore.Model; | ||
using Amazon.S3; | ||
using Amazon.S3.Transfer; | ||
using ConsoleAppFramework; | ||
using Documentation.Assembler.Deploying; | ||
using Elastic.Documentation.Serialization; | ||
using Elastic.Documentation.Tooling.Diagnostics.Console; | ||
using Elastic.Documentation.Tooling.Filters; | ||
using Microsoft.Extensions.Logging; | ||
using DescribeKeyValueStoreRequest = Amazon.CloudFront.Model.DescribeKeyValueStoreRequest; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. super nit: why the type alias here? |
||
|
||
namespace Documentation.Assembler.Cli; | ||
|
||
|
@@ -94,4 +101,112 @@ public async Task<int> Apply( | |
await collector.StopAsync(ctx); | ||
return collector.Errors; | ||
} | ||
|
||
/// <summary>Refreshes the redirects mapping in Cloudfront's KeyValueStore</summary> | ||
/// <param name="environment">The environment to build</param> | ||
/// <param name="redirectsFile">Path to the redirects mapping pre-generated by docs-assembler</param> | ||
/// <param name="ctx"></param> | ||
[Command("update-redirects")] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Out of curiousity do you know how long this takes? |
||
[ConsoleAppFilter<StopwatchFilter>] | ||
[ConsoleAppFilter<CatchExceptionFilter>] | ||
public async Task<int> UpdateRedirects( | ||
string environment, | ||
string redirectsFile = ".artifacts/assembly/redirects.json", | ||
Cancel ctx = default) | ||
{ | ||
AssignOutputLogger(); | ||
await using var collector = new ConsoleDiagnosticsCollector(logger, githubActionsService) | ||
{ | ||
NoHints = true | ||
}.StartAsync(ctx); | ||
|
||
if (!File.Exists(redirectsFile)) | ||
{ | ||
collector.EmitError(redirectsFile, "Redirects mapping does not exist."); | ||
await collector.StopAsync(ctx); | ||
return collector.Errors; | ||
} | ||
|
||
ConsoleApp.Log("Parsing redirects mapping"); | ||
var jsonContent = await File.ReadAllTextAsync(redirectsFile, ctx); | ||
var sourcedRedirects = JsonSerializer.Deserialize(jsonContent, SourceGenerationContext.Default.FrozenDictionaryStringString); | ||
|
||
if (sourcedRedirects is null) | ||
{ | ||
collector.EmitError(redirectsFile, "Redirects mapping is invalid."); | ||
await collector.StopAsync(ctx); | ||
return collector.Errors; | ||
} | ||
|
||
var kvsName = $"elastic-docs-v3-{environment}-redirects-kvs"; | ||
|
||
var cfClient = new AmazonCloudFrontClient(); | ||
var kvsClient = new AmazonCloudFrontKeyValueStoreClient(); | ||
|
||
ConsoleApp.Log("Describing KVS"); | ||
var describeResponse = await cfClient.DescribeKeyValueStoreAsync(new DescribeKeyValueStoreRequest { Name = kvsName }, ctx); | ||
|
||
var kvsArn = describeResponse.KeyValueStore.ARN; | ||
var eTag = describeResponse.ETag; | ||
var existingRedirects = new HashSet<string>(); | ||
|
||
var listKeysRequest = new ListKeysRequest { KvsARN = kvsArn }; | ||
ListKeysResponse listKeysResponse; | ||
|
||
do | ||
{ | ||
listKeysResponse = await kvsClient.ListKeysAsync(listKeysRequest, ctx); | ||
foreach (var item in listKeysResponse.Items) | ||
_ = existingRedirects.Add(item.Key); | ||
listKeysRequest.NextToken = listKeysResponse.NextToken; | ||
} | ||
while (!string.IsNullOrEmpty(listKeysResponse.NextToken)); | ||
|
||
var toPut = sourcedRedirects | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we add some error if this happens to go over the limit? If it goes over do we need N keyvalue stores? |
||
.Select(kvp => new PutKeyRequestListItem { Key = kvp.Key, Value = kvp.Value }); | ||
var toDelete = existingRedirects | ||
.Except(sourcedRedirects.Keys) | ||
.Select(k => new DeleteKeyRequestListItem { Key = k }); | ||
|
||
ConsoleApp.Log("Updating redirects in KVS"); | ||
const int batchSize = 500; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just out of curiousity, did you play with this to observe overall runtime of the command? |
||
|
||
eTag = await ProcessBatchUpdatesAsync(kvsClient, kvsArn, eTag, toPut, batchSize, "Puts", ctx); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No magic strings |
||
_ = await ProcessBatchUpdatesAsync(kvsClient, kvsArn, eTag, toDelete, batchSize, "Deletes", ctx); | ||
|
||
await collector.StopAsync(ctx); | ||
return collector.Errors; | ||
} | ||
|
||
private static async Task<string> ProcessBatchUpdatesAsync( | ||
IAmazonCloudFrontKeyValueStore kvsClient, | ||
string kvsArn, | ||
string eTag, | ||
IEnumerable<object> items, | ||
int batchSize, | ||
string operation, | ||
Cancel ctx) | ||
{ | ||
var enumerable = items.ToList(); | ||
for (var i = 0; i < enumerable.Count; i += batchSize) | ||
{ | ||
var batch = enumerable.Skip(i).Take(batchSize); | ||
var updateRequest = new UpdateKeysRequest | ||
{ | ||
KvsARN = kvsArn, | ||
IfMatch = eTag | ||
}; | ||
|
||
if (operation.Equals("Puts", StringComparison.InvariantCulture)) | ||
updateRequest.Puts = batch.Cast<PutKeyRequestListItem>().ToList(); | ||
else if (operation.Equals("Deletes", StringComparison.InvariantCulture)) | ||
updateRequest.Deletes = batch.Cast<DeleteKeyRequestListItem>().ToList(); | ||
|
||
var update = await kvsClient.UpdateKeysAsync(updateRequest, ctx); | ||
eTag = update.ETag; | ||
} | ||
|
||
return eTag; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -136,6 +136,10 @@ public async Task<int> BuildAll( | |
|
||
await cloner.WriteLinkRegistrySnapshot(checkoutResult.LinkRegistrySnapshot, ctx); | ||
|
||
var redirectsPath = Path.Combine(assembleContext.OutputDirectory.FullName, "redirects.json"); | ||
if (File.Exists(redirectsPath)) | ||
await githubActionsService.SetOutputAsync("redirects_artifact_path", redirectsPath); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will also need to be declared in the github action as output, will satisfy IDE checks nicely :) |
||
|
||
var sitemapBuilder = new SitemapBuilder(navigation.NavigationItems, assembleContext.WriteFileSystem, assembleContext.OutputDirectory); | ||
sitemapBuilder.Generate(); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we ensure
CollectRedirects
never adds these self referential redirects?