|
4 | 4 |
|
5 | 5 | using System.Diagnostics.CodeAnalysis; |
6 | 6 | using System.IO.Abstractions; |
| 7 | +using System.Text.Json; |
7 | 8 | using Actions.Core.Services; |
| 9 | +using Amazon.CloudFront; |
| 10 | +using Amazon.CloudFrontKeyValueStore; |
| 11 | +using Amazon.CloudFrontKeyValueStore.Model; |
8 | 12 | using Amazon.S3; |
9 | 13 | using Amazon.S3.Transfer; |
10 | 14 | using ConsoleAppFramework; |
11 | 15 | using Documentation.Assembler.Deploying; |
| 16 | +using Elastic.Documentation.Serialization; |
12 | 17 | using Elastic.Documentation.Tooling.Diagnostics.Console; |
| 18 | +using Elastic.Documentation.Tooling.Filters; |
13 | 19 | using Microsoft.Extensions.Logging; |
14 | 20 |
|
15 | 21 | namespace Documentation.Assembler.Cli; |
@@ -94,4 +100,107 @@ public async Task<int> Apply( |
94 | 100 | await collector.StopAsync(ctx); |
95 | 101 | return collector.Errors; |
96 | 102 | } |
| 103 | + |
| 104 | + /// <summary>Refreshes the redirects mapping in Cloudfront's KeyValueStore</summary> |
| 105 | + /// <param name="environment">The environment to build</param> |
| 106 | + /// <param name="redirectsFile">Path to the redirects mapping pre-generated by docs-assembler</param> |
| 107 | + /// <param name="ctx"></param> |
| 108 | + [Command("update-redirects")] |
| 109 | + [ConsoleAppFilter<StopwatchFilter>] |
| 110 | + [ConsoleAppFilter<CatchExceptionFilter>] |
| 111 | + public async Task<int> UpdateRedirects( |
| 112 | + string environment, |
| 113 | + string redirectsFile = ".artifacts/assembly/redirects.json", |
| 114 | + Cancel ctx = default) |
| 115 | + { |
| 116 | + AssignOutputLogger(); |
| 117 | + await using var collector = new ConsoleDiagnosticsCollector(logger, githubActionsService) |
| 118 | + { |
| 119 | + NoHints = true |
| 120 | + }.StartAsync(ctx); |
| 121 | + |
| 122 | + if (!File.Exists(redirectsFile)) |
| 123 | + { |
| 124 | + collector.EmitError(redirectsFile, "Redirects mapping does not exist."); |
| 125 | + await collector.StopAsync(ctx); |
| 126 | + return collector.Errors; |
| 127 | + } |
| 128 | + |
| 129 | + ConsoleApp.Log("Parsing redirects mapping"); |
| 130 | + var jsonContent = await File.ReadAllTextAsync(redirectsFile, ctx); |
| 131 | + var sourcedRedirects = JsonSerializer.Deserialize(jsonContent, SourceGenerationContext.Default.FrozenDictionaryStringString); |
| 132 | + |
| 133 | + if (sourcedRedirects is null) |
| 134 | + { |
| 135 | + collector.EmitError(redirectsFile, "Redirects mapping is invalid."); |
| 136 | + await collector.StopAsync(ctx); |
| 137 | + return collector.Errors; |
| 138 | + } |
| 139 | + |
| 140 | + var kvsName = $"elastic-docs-v3-{environment}-redirects-kvs"; |
| 141 | + |
| 142 | + var cfClient = new AmazonCloudFrontClient(); |
| 143 | + var kvsClient = new AmazonCloudFrontKeyValueStoreClient(); |
| 144 | + |
| 145 | + ConsoleApp.Log("Describing KVS"); |
| 146 | + var describeResponse = await cfClient.DescribeKeyValueStoreAsync(new Amazon.CloudFront.Model.DescribeKeyValueStoreRequest() |
| 147 | + { |
| 148 | + Name = kvsName |
| 149 | + }, ctx); |
| 150 | + |
| 151 | + var kvsArn = describeResponse.KeyValueStore.ARN; |
| 152 | + var eTag = describeResponse.ETag; |
| 153 | + var existingRedirects = new HashSet<string>(); |
| 154 | + |
| 155 | + var listKeysRequest = new ListKeysRequest { KvsARN = kvsArn }; |
| 156 | + ListKeysResponse listKeysResponse; |
| 157 | + |
| 158 | + do |
| 159 | + { |
| 160 | + listKeysResponse = await kvsClient.ListKeysAsync(listKeysRequest, ctx); |
| 161 | + foreach (var item in listKeysResponse.Items) |
| 162 | + _ = existingRedirects.Add(item.Key); |
| 163 | + listKeysRequest.NextToken = listKeysResponse.NextToken; |
| 164 | + } |
| 165 | + while (!string.IsNullOrEmpty(listKeysResponse.NextToken)); |
| 166 | + |
| 167 | + var toPut = sourcedRedirects |
| 168 | + .Select(kvp => new PutKeyRequestListItem { Key = kvp.Key, Value = kvp.Value }) |
| 169 | + .ToList(); |
| 170 | + var toDelete = existingRedirects |
| 171 | + .Except(sourcedRedirects.Keys) |
| 172 | + .Select(k => new DeleteKeyRequestListItem { Key = k }) |
| 173 | + .ToList(); |
| 174 | + |
| 175 | + ConsoleApp.Log("Updating redirects in KVS"); |
| 176 | + const int batchSize = 500; |
| 177 | + |
| 178 | + for (var i = 0; i < toPut.Count; i += batchSize) |
| 179 | + { |
| 180 | + var batch = toPut.Skip(i).Take(batchSize).ToList(); |
| 181 | + var update = await kvsClient.UpdateKeysAsync(new UpdateKeysRequest |
| 182 | + { |
| 183 | + KvsARN = kvsArn, |
| 184 | + IfMatch = eTag, |
| 185 | + Puts = batch |
| 186 | + }, ctx); |
| 187 | + eTag = update.ETag; |
| 188 | + } |
| 189 | + |
| 190 | + for (var i = 0; i < toDelete.Count; i += batchSize) |
| 191 | + { |
| 192 | + var batch = toDelete.Skip(i).Take(batchSize).ToList(); |
| 193 | + var update = await kvsClient.UpdateKeysAsync(new UpdateKeysRequest |
| 194 | + { |
| 195 | + KvsARN = kvsArn, |
| 196 | + IfMatch = eTag, |
| 197 | + Deletes = batch |
| 198 | + }, ctx); |
| 199 | + eTag = update.ETag; |
| 200 | + } |
| 201 | + |
| 202 | + await collector.StopAsync(ctx); |
| 203 | + return collector.Errors; |
| 204 | + } |
| 205 | + |
97 | 206 | } |
0 commit comments