Skip to content

Commit a6a0df6

Browse files
authored
Specialized index command aliases with more options (#1934)
* Add specialized index commands * Fix --version and --help being too noisy * Ensure exporters are start and stopped during isolated builds * Improve export logging a tad * Ensure assemble build shows no hints but we now expose --show-hints to enable it * build failures
1 parent 7c32cc6 commit a6a0df6

File tree

28 files changed

+690
-80
lines changed

28 files changed

+690
-80
lines changed

aspire/AppHost.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// ReSharper disable NotAccessedVariable
1414

1515
var logLevel = LogLevel.Information;
16-
GlobalCommandLine.Process(ref args, ref logLevel, out var skipPrivateRepositories);
16+
GlobalCommandLine.Process(ref args, ref logLevel, out var skipPrivateRepositories, out _);
1717
var globalArguments = new List<string>();
1818
if (skipPrivateRepositories)
1919
globalArguments.Add("--skip-private-repositories");

src/Elastic.Documentation.Configuration/DocumentationEndpoints.cs

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,45 @@
22
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
33
// See the LICENSE file in the project root for more information
44

5+
using System.Security.Cryptography.X509Certificates;
6+
57
namespace Elastic.Documentation.Configuration;
68

7-
public record DocumentationEndpoints
9+
public class DocumentationEndpoints
810
{
911
public required ElasticsearchEndpoint Elasticsearch { get; init; }
1012
}
1113

12-
public record ElasticsearchEndpoint
14+
public class ElasticsearchEndpoint
1315
{
14-
public static ElasticsearchEndpoint Default { get; } = new ElasticsearchEndpoint { Uri = new Uri("https://localhost:9200") };
16+
public static ElasticsearchEndpoint Default { get; } = new() { Uri = new Uri("https://localhost:9200") };
17+
18+
public required Uri Uri { get; set; }
19+
public string? Username { get; set; }
20+
public string? Password { get; set; }
21+
public string? ApiKey { get; set; }
22+
23+
// inference options
24+
public int SearchNumThreads { get; set; } = 8;
25+
public int IndexNumThreads { get; set; } = 8;
26+
27+
// index options
28+
public string IndexNamePrefix { get; set; } = "semantic-docs";
29+
30+
// channel buffer options
31+
public int BufferSize { get; set; } = 100;
32+
public int MaxRetries { get; set; } = 3;
33+
34+
35+
// connection options
36+
public bool DebugMode { get; set; }
37+
public string? CertificateFingerprint { get; set; }
38+
public string? ProxyAddress { get; set; }
39+
public string? ProxyPassword { get; set; }
40+
public string? ProxyUsername { get; set; }
1541

16-
public required Uri Uri { get; init; }
17-
public string? Username { get; init; }
18-
public string? Password { get; init; }
19-
public string? ApiKey { get; init; }
42+
public bool DisableSslVerification { get; set; }
43+
public X509Certificate? Certificate { get; set; }
44+
public bool CertificateIsNotRoot { get; set; }
45+
public int? BootstrapTimeout { get; set; }
2046
}

src/Elastic.Documentation.ServiceDefaults/AppDefaultsExtensions.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
namespace Elastic.Documentation.ServiceDefaults;
1515

16+
public record CliInvocation(bool IsHelpOrVersion);
17+
1618
public static class AppDefaultsExtensions
1719
{
1820
public static TBuilder AddDocumentationServiceDefaults<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder
@@ -26,7 +28,7 @@ public static TBuilder AddDocumentationServiceDefaults<TBuilder>(this TBuilder b
2628
public static TBuilder AddDocumentationServiceDefaults<TBuilder>(this TBuilder builder, ref string[] args, LogLevel? defaultLogLevel = null, Action<IServiceCollection, ConfigurationFileProvider>? configure = null) where TBuilder : IHostApplicationBuilder
2729
{
2830
var logLevel = defaultLogLevel ?? LogLevel.Information;
29-
GlobalCommandLine.Process(ref args, ref logLevel, out var skipPrivateRepositories);
31+
GlobalCommandLine.Process(ref args, ref logLevel, out var skipPrivateRepositories, out var isHelpOrVersion);
3032

3133
var services = builder.Services;
3234
_ = services
@@ -36,6 +38,7 @@ public static TBuilder AddDocumentationServiceDefaults<TBuilder>(this TBuilder b
3638
configure?.Invoke(s, p);
3739
});
3840
_ = builder.Services.AddElasticDocumentationLogging(logLevel);
41+
_ = services.AddSingleton(new CliInvocation(isHelpOrVersion));
3942

4043
return builder.AddServiceDefaults();
4144
}

src/Elastic.Documentation/Diagnostics/DiagnosticsCollector.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public class DiagnosticsCollector(IReadOnlyCollection<IDiagnosticsOutput> output
2929

3030
public ConcurrentBag<string> CrossLinks { get; } = [];
3131

32-
public bool NoHints { get; init; }
32+
public bool NoHints { get; set; }
3333

3434
public DiagnosticsCollector StartAsync(Cancel ctx)
3535
{

src/Elastic.Documentation/Diagnostics/IDiagnosticsCollector.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ public interface IDiagnosticsCollector : IAsyncDisposable, IHostedService
1414
int Errors { get; }
1515
int Hints { get; }
1616

17+
bool NoHints { get; set; }
18+
1719
DiagnosticsChannel Channel { get; }
1820
ConcurrentBag<string> CrossLinks { get; }
1921
HashSet<string> OffendingFiles { get; }

src/Elastic.Documentation/Exporter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public enum Exporter
1111
Html,
1212
LLMText,
1313
Elasticsearch,
14-
SemanticElasticsearch,
14+
ElasticsearchNoSemantic,
1515
Configuration,
1616
DocumentationState,
1717
LinkMetadata,

src/Elastic.Documentation/GlobalCommandLine.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,15 @@ namespace Elastic.Documentation;
88

99
public static class GlobalCommandLine
1010
{
11-
public static void Process(ref string[] args, ref LogLevel defaultLogLevel, out bool skipPrivateRepositories)
11+
public static void Process(
12+
ref string[] args,
13+
ref LogLevel defaultLogLevel,
14+
out bool skipPrivateRepositories,
15+
out bool isHelpOrVersion
16+
)
1217
{
1318
skipPrivateRepositories = false;
19+
isHelpOrVersion = false;
1420
var newArgs = new List<string>();
1521
for (var i = 0; i < args.Length; i++)
1622
{
@@ -22,8 +28,11 @@ public static void Process(ref string[] args, ref LogLevel defaultLogLevel, out
2228
}
2329
else if (args[i] == "--skip-private-repositories")
2430
skipPrivateRepositories = true;
25-
else if (args[i] == "--inject")
26-
skipPrivateRepositories = true;
31+
else if (args[i] is "--help" or "--version")
32+
{
33+
isHelpOrVersion = true;
34+
newArgs.Add(args[i]);
35+
}
2736
else
2837
newArgs.Add(args[i]);
2938
}

src/Elastic.Markdown/Exporters/ElasticsearchMarkdownExporter.cs

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.IO.Abstractions;
66
using Elastic.Channels;
77
using Elastic.Documentation.Configuration;
8-
using Elastic.Documentation.Configuration.Assembler;
98
using Elastic.Documentation.Diagnostics;
109
using Elastic.Documentation.Search;
1110
using Elastic.Documentation.Serialization;
@@ -20,23 +19,24 @@
2019

2120
namespace Elastic.Markdown.Exporters;
2221

23-
public class ElasticsearchMarkdownExporter(ILoggerFactory logFactory, IDiagnosticsCollector collector, DocumentationEndpoints endpoints)
22+
public class ElasticsearchMarkdownExporter(ILoggerFactory logFactory, IDiagnosticsCollector collector, string indexNamespace, DocumentationEndpoints endpoints)
2423
: ElasticsearchMarkdownExporterBase<CatalogIndexChannelOptions<DocumentationDocument>, CatalogIndexChannel<DocumentationDocument>>
2524
(logFactory, collector, endpoints)
2625
{
2726
/// <inheritdoc />
2827
protected override CatalogIndexChannelOptions<DocumentationDocument> NewOptions(DistributedTransport transport) => new(transport)
2928
{
3029
GetMapping = () => CreateMapping(null),
31-
IndexFormat = "documentation{0:yyyy.MM.dd.HHmmss}",
32-
ActiveSearchAlias = "documentation"
30+
GetMappingSettings = () => CreateMappingSetting(),
31+
IndexFormat = $"{Endpoint.IndexNamePrefix.ToLowerInvariant()}-{indexNamespace.ToLowerInvariant()}-{{0:yyyy.MM.dd.HHmmss}}",
32+
ActiveSearchAlias = $"{Endpoint.IndexNamePrefix}-{indexNamespace.ToLowerInvariant()}",
3333
};
3434

3535
/// <inheritdoc />
3636
protected override CatalogIndexChannel<DocumentationDocument> NewChannel(CatalogIndexChannelOptions<DocumentationDocument> options) => new(options);
3737
}
3838

39-
public class ElasticsearchMarkdownSemanticExporter(PublishEnvironment environment, ILoggerFactory logFactory, IDiagnosticsCollector collector, DocumentationEndpoints endpoints)
39+
public class ElasticsearchMarkdownSemanticExporter(ILoggerFactory logFactory, IDiagnosticsCollector collector, string indexNamespace, DocumentationEndpoints endpoints)
4040
: ElasticsearchMarkdownExporterBase<SemanticIndexChannelOptions<DocumentationDocument>, SemanticIndexChannel<DocumentationDocument>>
4141
(logFactory, collector, endpoints)
4242
{
@@ -45,20 +45,23 @@ public class ElasticsearchMarkdownSemanticExporter(PublishEnvironment environmen
4545
{
4646
GetMapping = (inferenceId, _) => CreateMapping(inferenceId),
4747
GetMappingSettings = (_, _) => CreateMappingSetting(),
48-
IndexFormat = $"semantic-docs-{environment.Name}-{{0:yyyy.MM.dd.HHmmss}}",
49-
ActiveSearchAlias = $"semantic-docs-{environment.Name}",
50-
IndexNumThreads = IndexNumThreads,
51-
InferenceCreateTimeout = TimeSpan.FromMinutes(4)
48+
IndexFormat = $"{Endpoint.IndexNamePrefix.ToLowerInvariant()}-{indexNamespace.ToLowerInvariant()}-{{0:yyyy.MM.dd.HHmmss}}",
49+
ActiveSearchAlias = $"{Endpoint.IndexNamePrefix}-{indexNamespace.ToLowerInvariant()}",
50+
IndexNumThreads = Endpoint.IndexNumThreads,
51+
SearchNumThreads = Endpoint.SearchNumThreads,
52+
InferenceCreateTimeout = TimeSpan.FromMinutes(Endpoint.BootstrapTimeout ?? 4)
5253
};
5354

5455
/// <inheritdoc />
5556
protected override SemanticIndexChannel<DocumentationDocument> NewChannel(SemanticIndexChannelOptions<DocumentationDocument> options) => new(options);
5657
}
5758

59+
5860
public abstract class ElasticsearchMarkdownExporterBase<TChannelOptions, TChannel>(
5961
ILoggerFactory logFactory,
6062
IDiagnosticsCollector collector,
61-
DocumentationEndpoints endpoints)
63+
DocumentationEndpoints endpoints
64+
)
6265
: IMarkdownExporter, IDisposable
6366
where TChannelOptions : CatalogIndexChannelOptionsBase<DocumentationDocument>
6467
where TChannel : CatalogIndexChannel<DocumentationDocument, TChannelOptions>
@@ -69,7 +72,7 @@ public abstract class ElasticsearchMarkdownExporterBase<TChannelOptions, TChanne
6972
protected abstract TChannelOptions NewOptions(DistributedTransport transport);
7073
protected abstract TChannel NewChannel(TChannelOptions options);
7174

72-
protected int IndexNumThreads => 8;
75+
protected ElasticsearchEndpoint Endpoint { get; } = endpoints.Elasticsearch;
7376

7477
protected static string CreateMappingSetting() =>
7578
// language=json
@@ -97,7 +100,6 @@ protected static string CreateMappingSetting() =>
97100
""";
98101

99102
protected static string CreateMapping(string? inferenceId) =>
100-
// langugage=json
101103
$$"""
102104
{
103105
"properties": {
@@ -131,15 +133,13 @@ protected static string CreateMapping(string? inferenceId) =>
131133
""";
132134

133135
private static string AbstractMapping() =>
134-
// langugage=json
135136
"""
136137
, "abstract": {
137138
"type": "text"
138139
}
139140
""";
140141

141142
private static string InferenceMapping(string inferenceId) =>
142-
// langugage=json
143143
$"""
144144
"type": "semantic_text",
145145
"inference_id": "{inferenceId}"
@@ -159,12 +159,26 @@ public async ValueTask StartAsync(Cancel ctx = default)
159159
return;
160160

161161
var es = endpoints.Elasticsearch;
162+
162163
var configuration = new ElasticsearchConfiguration(es.Uri)
163164
{
164165
Authentication = es.ApiKey is { } apiKey
165166
? new ApiKey(apiKey)
166-
: es.Username is { } username && es.Password is { } password
167+
: es is { Username: { } username, Password: { } password }
167168
? new BasicAuthentication(username, password)
169+
: null,
170+
EnableHttpCompression = true,
171+
DebugMode = Endpoint.DebugMode,
172+
CertificateFingerprint = Endpoint.CertificateFingerprint,
173+
ProxyAddress = Endpoint.ProxyAddress,
174+
ProxyPassword = Endpoint.ProxyPassword,
175+
ProxyUsername = Endpoint.ProxyUsername,
176+
ServerCertificateValidationCallback = Endpoint.DisableSslVerification
177+
? CertificateValidations.AllowAll
178+
: Endpoint.Certificate is { } cert
179+
? Endpoint.CertificateIsNotRoot
180+
? CertificateValidations.AuthorityPartOfChain(cert)
181+
: CertificateValidations.AuthorityIsRoot(cert)
168182
: null
169183
};
170184

@@ -173,14 +187,20 @@ public async ValueTask StartAsync(Cancel ctx = default)
173187
//The max num threads per allocated node, from testing its best to limit our max concurrency
174188
//producing to this number as well
175189
var options = NewOptions(transport);
190+
var i = 0;
176191
options.BufferOptions = new BufferOptions
177192
{
178-
OutboundBufferMaxSize = 100,
179-
ExportMaxConcurrency = IndexNumThreads,
180-
ExportMaxRetries = 3
193+
OutboundBufferMaxSize = Endpoint.BufferSize,
194+
ExportMaxConcurrency = Endpoint.IndexNumThreads,
195+
ExportMaxRetries = Endpoint.MaxRetries,
181196
};
182197
options.SerializerContext = SourceGenerationContext.Default;
183-
options.ExportBufferCallback = () => _logger.LogInformation("Exported buffer to Elasticsearch");
198+
options.ExportBufferCallback = () =>
199+
{
200+
var count = Interlocked.Increment(ref i);
201+
_logger.LogInformation("Exported {Count} documents to Elasticsearch index {Format}",
202+
count * Endpoint.BufferSize, options.IndexFormat);
203+
};
184204
options.ExportExceptionCallback = e => _logger.LogError(e, "Failed to export document");
185205
options.ServerRejectionCallback = items => _logger.LogInformation("Server rejection: {Rejection}", items.First().Item2);
186206
_channel = NewChannel(options);
@@ -206,7 +226,7 @@ public async ValueTask StopAsync(Cancel ctx = default)
206226
_logger.LogInformation("Applying aliases to {Index}", _channel.IndexName);
207227
var swapped = await _channel.ApplyAliasesAsync(ctx);
208228
if (!swapped)
209-
collector.EmitGlobalError($"{nameof(ElasticsearchMarkdownExporter)} failed to apply aliases to index {_channel.IndexName}");
229+
collector.EmitGlobalError($"${nameof(ElasticsearchMarkdownExporter)} failed to apply aliases to index {_channel.IndexName}");
210230
}
211231

212232
public void Dispose()

src/Elastic.Markdown/Exporters/ExporterExtensions.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public static IReadOnlyCollection<IMarkdownExporter> CreateMarkdownExporters(
1515
this IReadOnlySet<Exporter> exportOptions,
1616
ILoggerFactory logFactory,
1717
IDocumentationConfigurationContext context,
18-
PublishEnvironment? environment = null
18+
string indexNamespace
1919
)
2020
{
2121
var markdownExporters = new List<IMarkdownExporter>(3);
@@ -24,13 +24,9 @@ public static IReadOnlyCollection<IMarkdownExporter> CreateMarkdownExporters(
2424
if (exportOptions.Contains(Exporter.Configuration))
2525
markdownExporters.Add(new ConfigurationExporter(logFactory, context.ConfigurationFileProvider, context));
2626
if (exportOptions.Contains(Exporter.Elasticsearch))
27-
markdownExporters.Add(new ElasticsearchMarkdownExporter(logFactory, context.Collector, context.Endpoints));
28-
if (exportOptions.Contains(Exporter.SemanticElasticsearch))
29-
{
30-
if (environment is null)
31-
throw new ArgumentNullException(nameof(environment), "A publish environment is required when using the semantic elasticsearch exporter");
32-
markdownExporters.Add(new ElasticsearchMarkdownSemanticExporter(environment, logFactory, context.Collector, context.Endpoints));
33-
}
27+
markdownExporters.Add(new ElasticsearchMarkdownSemanticExporter(logFactory, context.Collector, indexNamespace, context.Endpoints));
28+
if (exportOptions.Contains(Exporter.ElasticsearchNoSemantic))
29+
markdownExporters.Add(new ElasticsearchMarkdownExporter(logFactory, context.Collector, indexNamespace, context.Endpoints));
3430
return markdownExporters;
3531
}
3632
}

src/authoring/Elastic.Documentation.Refactor/Tracking/LocalChangesService.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ IConfigurationContext configurationContext
1919
{
2020
private readonly ILogger _logger = logFactory.CreateLogger<LocalChangeTrackingService>();
2121

22-
public async Task<bool> ValidateRedirects(IDiagnosticsCollector collector, string? path, FileSystem fs, Cancel ctx)
22+
public Task<bool> ValidateRedirects(IDiagnosticsCollector collector, string? path, FileSystem fs)
2323
{
2424
var runningOnCi = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GITHUB_ACTIONS"));
2525

@@ -28,21 +28,21 @@ public async Task<bool> ValidateRedirects(IDiagnosticsCollector collector, strin
2828
if (!redirectFile.Source.Exists)
2929
{
3030
collector.EmitError(redirectFile.Source, "File does not exist");
31-
return false;
31+
return Task.FromResult(false);
3232
}
3333

3434
var redirects = redirectFile.Redirects;
3535
if (redirects is null)
3636
{
3737
collector.EmitError(redirectFile.Source, "It was not possible to parse the redirects file.");
38-
return false;
38+
return Task.FromResult(false);
3939
}
4040

4141
var root = Paths.DetermineSourceDirectoryRoot(buildContext.DocumentationSourceDirectory);
4242
if (root is null)
4343
{
4444
collector.EmitError(redirectFile.Source, $"Unable to determine the root of the source directory {buildContext.DocumentationSourceDirectory}.");
45-
return false;
45+
return Task.FromResult(false);
4646
}
4747
var relativePath = Path.GetRelativePath(root.FullName, buildContext.DocumentationSourceDirectory.FullName);
4848
_logger.LogInformation("Using relative path {RelativePath} for validating changes", relativePath);
@@ -87,7 +87,6 @@ public async Task<bool> ValidateRedirects(IDiagnosticsCollector collector, strin
8787
_logger.LogInformation("Found {Count} changes that still require updates to: {RedirectFile}", missingCount, relativeRedirectFile);
8888
}
8989

90-
await collector.StopAsync(ctx);
91-
return collector.Errors == 0;
90+
return Task.FromResult(collector.Errors == 0);
9291
}
9392
}

0 commit comments

Comments
 (0)