From 3c923b9b28dc561c64fc14ae701a675c7c4ff5c3 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Wed, 21 May 2025 18:33:14 +0200 Subject: [PATCH 1/2] Add handling for speculative matches in content source logic This will flag version branches as a speculative match ensuring we'll onboard new version branches before they are actively marked as current / next. If a current branch is already a version branch we will only build new version branches speculatively. This also ensures new repositories will speculatively build for `main` / `master` or version branches. --- .github/workflows/preview-build.yml | 2 +- actions/assembler-match/action.yml | 2 + .../Assembler/AssemblyConfiguration.cs | 46 ++++++++++++++++--- .../SemVersion.cs | 2 +- .../Myst/Directives/VersionBlock.cs | 1 + .../Myst/FrontMatter/AllVersions.cs | 1 + .../Myst/FrontMatter/Applicability.cs | 1 + .../Myst/Roles/AppliesTo/AppliesToRole.cs | 1 + .../Cli/ContentSourceCommands.cs | 4 +- .../docs-builder/Cli/CheckForUpdatesFilter.cs | 1 + .../Directives/VersionTests.cs | 1 + 11 files changed, 53 insertions(+), 9 deletions(-) rename src/{Elastic.Markdown/Helpers => Elastic.Documentation}/SemVersion.cs (99%) diff --git a/.github/workflows/preview-build.yml b/.github/workflows/preview-build.yml index 7935e0daf..ff4b854fa 100644 --- a/.github/workflows/preview-build.yml +++ b/.github/workflows/preview-build.yml @@ -81,7 +81,7 @@ jobs: run: | echo "Non sensitive data, echo'ing here temporarily to validate this job before connecting it further into the build job" echo "content-source-match=${{ steps.event-check.outputs.content-source-match != '' && steps.event-check.outputs.content-source-match || steps.match.outputs.content-source-match }}" - echo "content-source-name=${{ steps.event-check.outputs.content-source-next != '' && steps.event-check.outputs.content-source-next || steps.match.outputs.content-source-next }}" + echo "content-source-next=${{ steps.event-check.outputs.content-source-next != '' && steps.event-check.outputs.content-source-next || steps.match.outputs.content-source-next }}" echo "content-source-current=${{ steps.event-check.outputs.content-source-current != '' && steps.event-check.outputs.content-source-current || steps.match.outputs.content-source-current }}" echo "ref=${{ github.ref_name }}" echo "repo=${{ github.repository }}" diff --git a/actions/assembler-match/action.yml b/actions/assembler-match/action.yml index 7216dd852..4ec49c42e 100644 --- a/actions/assembler-match/action.yml +++ b/actions/assembler-match/action.yml @@ -20,6 +20,8 @@ outputs: description: "true/false indicating the branch acts as the next content source" content-source-current: description: "true/false indicating the branch acts as the current content source" + content-source-speculative: + description: "true/false speculative match, used to build version branches before they are marked as current/next" runs: using: 'docker' diff --git a/src/Elastic.Documentation.Configuration/Assembler/AssemblyConfiguration.cs b/src/Elastic.Documentation.Configuration/Assembler/AssemblyConfiguration.cs index 8a2ea01a3..da49bfbf9 100644 --- a/src/Elastic.Documentation.Configuration/Assembler/AssemblyConfiguration.cs +++ b/src/Elastic.Documentation.Configuration/Assembler/AssemblyConfiguration.cs @@ -2,6 +2,7 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using System.Text.RegularExpressions; using Elastic.Documentation.Serialization; using YamlDotNet.Serialization; using YamlStaticContext = Elastic.Documentation.Configuration.Serialization.YamlStaticContext; @@ -83,19 +84,45 @@ private static TRepository RepositoryDefaults(TRepository r, string /// . public ContentSourceMatch Match(string repository, string branchOrTag) { - var repositoryName = repository.Split('/').Last(); - var match = new ContentSourceMatch(null, null); + var match = new ContentSourceMatch(null, null, false); + var tokens = repository.Split('/'); + var repositoryName = tokens.Last(); + var owner = tokens.First(); + + if (tokens.Length < 2 || owner != "elastic") + return match; + if (ReferenceRepositories.TryGetValue(repositoryName, out var r)) { - if (r.GetBranch(ContentSource.Current) == branchOrTag) + var current = r.GetBranch(ContentSource.Current); + var next = r.GetBranch(ContentSource.Next); + var isVersionBranch = ContentSourceRegex.MatchVersionBranch().IsMatch(branchOrTag); + if (current == branchOrTag) match = match with { Current = ContentSource.Current }; - if (r.GetBranch(ContentSource.Next) == branchOrTag) + if (next == branchOrTag) match = match with { Next = ContentSource.Next }; + if (isVersionBranch && SemVersion.TryParse(branchOrTag + ".0", out var v)) + { + // if the current branch is a version, only speculatively match if branch is actually a new version + if (SemVersion.TryParse(current + ".0", out var currentVersion)) + { + if (v >= currentVersion) + match = match with { Speculative = true }; + } + // assume we are newly onboarding the repository to current/next + else + match = match with { Speculative = true }; + } return match; } if (repositoryName != NarrativeRepository.RepositoryName) - return match; + { + // this is an unknown new elastic repository + var isVersionBranch = ContentSourceRegex.MatchVersionBranch().IsMatch(branchOrTag); + if (isVersionBranch || branchOrTag == "main" || branchOrTag == "master") + return match with { Speculative = true }; + } if (Narrative.GetBranch(ContentSource.Current) == branchOrTag) match = match with { Current = ContentSource.Current }; @@ -105,5 +132,12 @@ public ContentSourceMatch Match(string repository, string branchOrTag) return match; } - public record ContentSourceMatch(ContentSource? Current, ContentSource? Next); + public record ContentSourceMatch(ContentSource? Current, ContentSource? Next, bool Speculative); + +} + +internal static partial class ContentSourceRegex +{ + [GeneratedRegex(@"^\d+\.\d+$", RegexOptions.IgnoreCase, "en-US")] + public static partial Regex MatchVersionBranch(); } diff --git a/src/Elastic.Markdown/Helpers/SemVersion.cs b/src/Elastic.Documentation/SemVersion.cs similarity index 99% rename from src/Elastic.Markdown/Helpers/SemVersion.cs rename to src/Elastic.Documentation/SemVersion.cs index bcbd98ac5..f305c2cbe 100644 --- a/src/Elastic.Markdown/Helpers/SemVersion.cs +++ b/src/Elastic.Documentation/SemVersion.cs @@ -6,7 +6,7 @@ using System.Globalization; using System.Text.RegularExpressions; -namespace Elastic.Markdown.Helpers; +namespace Elastic.Documentation; /// /// A semver2 compatible version. diff --git a/src/Elastic.Markdown/Myst/Directives/VersionBlock.cs b/src/Elastic.Markdown/Myst/Directives/VersionBlock.cs index 0bb3b0841..ccb2d0aa3 100644 --- a/src/Elastic.Markdown/Myst/Directives/VersionBlock.cs +++ b/src/Elastic.Markdown/Myst/Directives/VersionBlock.cs @@ -2,6 +2,7 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using Elastic.Documentation; using Elastic.Markdown.Diagnostics; using Elastic.Markdown.Helpers; using static System.StringSplitOptions; diff --git a/src/Elastic.Markdown/Myst/FrontMatter/AllVersions.cs b/src/Elastic.Markdown/Myst/FrontMatter/AllVersions.cs index ff9a2f8da..d12deeb6a 100644 --- a/src/Elastic.Markdown/Myst/FrontMatter/AllVersions.cs +++ b/src/Elastic.Markdown/Myst/FrontMatter/AllVersions.cs @@ -2,6 +2,7 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using Elastic.Documentation; using Elastic.Markdown.Helpers; using YamlDotNet.Core; using YamlDotNet.Core.Events; diff --git a/src/Elastic.Markdown/Myst/FrontMatter/Applicability.cs b/src/Elastic.Markdown/Myst/FrontMatter/Applicability.cs index 043eca238..1bea59ded 100644 --- a/src/Elastic.Markdown/Myst/FrontMatter/Applicability.cs +++ b/src/Elastic.Markdown/Myst/FrontMatter/Applicability.cs @@ -5,6 +5,7 @@ using System.Collections; using System.Diagnostics.CodeAnalysis; using System.Text; +using Elastic.Documentation; using Elastic.Markdown.Helpers; using YamlDotNet.Serialization; diff --git a/src/Elastic.Markdown/Myst/Roles/AppliesTo/AppliesToRole.cs b/src/Elastic.Markdown/Myst/Roles/AppliesTo/AppliesToRole.cs index 3f3da4411..e1da4b3a0 100644 --- a/src/Elastic.Markdown/Myst/Roles/AppliesTo/AppliesToRole.cs +++ b/src/Elastic.Markdown/Myst/Roles/AppliesTo/AppliesToRole.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information using System.Diagnostics; +using Elastic.Documentation; using Elastic.Markdown.Diagnostics; using Elastic.Markdown.Helpers; using Elastic.Markdown.Myst.FrontMatter; diff --git a/src/tooling/docs-assembler/Cli/ContentSourceCommands.cs b/src/tooling/docs-assembler/Cli/ContentSourceCommands.cs index 6976bf78a..8a5d04977 100644 --- a/src/tooling/docs-assembler/Cli/ContentSourceCommands.cs +++ b/src/tooling/docs-assembler/Cli/ContentSourceCommands.cs @@ -55,12 +55,13 @@ public async Task Match([Argument] string? repository = null, [Argument] st AllowIndexing = false }; var matches = assembleContext.Configuration.Match(repo, refName); - if (matches is { Current: null, Next: null }) + if (matches is { Current: null, Next: null, Speculative: false }) { logger.LogInformation("'{Repository}' '{BranchOrTag}' combination not found in configuration.", repo, refName); await githubActionsService.SetOutputAsync("content-source-match", "false"); await githubActionsService.SetOutputAsync("content-source-next", "false"); await githubActionsService.SetOutputAsync("content-source-current", "false"); + await githubActionsService.SetOutputAsync("content-source-speculative", "false"); } else { @@ -72,6 +73,7 @@ public async Task Match([Argument] string? repository = null, [Argument] st await githubActionsService.SetOutputAsync("content-source-match", "true"); await githubActionsService.SetOutputAsync("content-source-next", matches.Next is not null ? "true" : "false"); await githubActionsService.SetOutputAsync("content-source-current", matches.Current is not null ? "true" : "false"); + await githubActionsService.SetOutputAsync("content-source-speculative", matches.Speculative ? "true" : "false"); } await collector.StopAsync(ctx); diff --git a/src/tooling/docs-builder/Cli/CheckForUpdatesFilter.cs b/src/tooling/docs-builder/Cli/CheckForUpdatesFilter.cs index 4e03ed6f6..0e55dc96d 100644 --- a/src/tooling/docs-builder/Cli/CheckForUpdatesFilter.cs +++ b/src/tooling/docs-builder/Cli/CheckForUpdatesFilter.cs @@ -4,6 +4,7 @@ using System.Reflection; using ConsoleAppFramework; +using Elastic.Documentation; using Elastic.Markdown.Helpers; using Elastic.Markdown.IO; diff --git a/tests/Elastic.Markdown.Tests/Directives/VersionTests.cs b/tests/Elastic.Markdown.Tests/Directives/VersionTests.cs index d2fc72f29..47e79a4a9 100644 --- a/tests/Elastic.Markdown.Tests/Directives/VersionTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/VersionTests.cs @@ -2,6 +2,7 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using Elastic.Documentation; using Elastic.Markdown.Helpers; using Elastic.Markdown.Myst.Directives; using FluentAssertions; From aee7ce34958fdf22b6a5185e45ea916180f69087 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Wed, 21 May 2025 18:48:15 +0200 Subject: [PATCH 2/2] Add support for 'content-source-speculative' in preview builds.` Updated the preview-build workflow to include handling for the new 'content-source-speculative' output. This ensures proper evaluation and processing of speculative content sources in relevant steps of the workflow. --- .github/workflows/preview-build.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/preview-build.yml b/.github/workflows/preview-build.yml index ff4b854fa..e9ddc8497 100644 --- a/.github/workflows/preview-build.yml +++ b/.github/workflows/preview-build.yml @@ -61,6 +61,7 @@ jobs: content-source-match: ${{ steps.event-check.outputs.content-source-match != '' && steps.event-check.outputs.content-source-match || steps.match.outputs.content-source-match }} content-source-next: ${{ steps.event-check.outputs.content-source-next != '' && steps.event-check.outputs.content-source-next || steps.match.outputs.content-source-next }} content-source-current: ${{ steps.event-check.outputs.content-source-current != '' && steps.event-check.outputs.content-source-current || steps.match.outputs.content-source-current }} + content-source-speculative: ${{ steps.event-check.outputs.content-source-speculative != '' && steps.event-check.outputs.content-source-speculative || steps.match.outputs.content-source-speculative }} steps: - name: Not a push event id: event-check @@ -70,6 +71,7 @@ jobs: echo "content-source-match=true" >> $GITHUB_OUTPUT echo "content-source-next=false" >> $GITHUB_OUTPUT echo "content-source-current=false" >> $GITHUB_OUTPUT + echo "content-source-speculative=false" >> $GITHUB_OUTPUT - name: Match for push events id: match if: contains(fromJSON('["push"]'), github.event_name) @@ -83,6 +85,7 @@ jobs: echo "content-source-match=${{ steps.event-check.outputs.content-source-match != '' && steps.event-check.outputs.content-source-match || steps.match.outputs.content-source-match }}" echo "content-source-next=${{ steps.event-check.outputs.content-source-next != '' && steps.event-check.outputs.content-source-next || steps.match.outputs.content-source-next }}" echo "content-source-current=${{ steps.event-check.outputs.content-source-current != '' && steps.event-check.outputs.content-source-current || steps.match.outputs.content-source-current }}" + echo "content-source-speculative=${{ steps.event-check.outputs.content-source-speculative != '' && steps.event-check.outputs.content-source-speculative || steps.match.outputs.content-source-speculative }}" echo "ref=${{ github.ref_name }}" echo "repo=${{ github.repository }}" @@ -233,7 +236,11 @@ jobs: if: | env.MATCH == 'true' && (contains(fromJSON('["push", "workflow_dispatch"]'), github.event_name) - && (needs.match.outputs.content-source-current == 'true' || needs.match.outputs.content-source-next == 'true') + && ( + needs.match.outputs.content-source-current == 'true' + || needs.match.outputs.content-source-next == 'true' + || needs.match.outputs.content-source-speculative == 'true' + ) && steps.s3-upload.outcome == 'success') uses: elastic/docs-builder/actions/update-link-index@main