Skip to content

Commit a95d0d0

Browse files
lcawlcotti
authored andcommitted
Replace changelog sanitize_private_links with link_allow_repos (#3029)
1 parent dd3a471 commit a95d0d0

14 files changed

Lines changed: 818 additions & 998 deletions

File tree

config/changelog.example.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -212,9 +212,13 @@ bundle:
212212
output_directory: docs/releases
213213
# Whether to resolve (copy contents) by default
214214
resolve: true
215-
# When true, PR/issue links to repos marked private in assembler.yml are rewritten in bundle output
216-
# (requires resolve: true). Option-based: --sanitize-private-links / --no-sanitize-private-links.
217-
sanitize_private_links: false
215+
# PR/issue link allowlist: when set (including []), only links to these owner/repo pairs are kept
216+
# in bundle output; others are rewritten to '# PRIVATE:' sentinels (requires resolve: true).
217+
# When omitted, no link filtering is applied.
218+
# Add your repository and any others whose PR/issue links should appear in published docs.
219+
# link_allow_repos:
220+
# - elastic/elasticsearch
221+
# - elastic/kibana
218222
# Optional: default GitHub repo name applied to all profiles that do not specify their own.
219223
# Used by the {changelog} directive to generate correct PR/issue links when the product ID
220224
# differs from the GitHub repository name. Can be overridden per profile.
@@ -238,9 +242,6 @@ bundle:
238242
# output: "elasticsearch-{version}.yaml"
239243
# # Optional: override the products array written to the bundle output.
240244
# # output_products: "elasticsearch {version}"
241-
# # Optional: override bundle.sanitize_private_links for this profile only (requires bundle.resolve: true).
242-
# sanitize_private_links: true
243-
244245
# Example: GitHub release profile (fetches PR list directly from a GitHub release)
245246
# Use when you want to bundle or remove changelogs based on a published GitHub release.
246247
# elasticsearch-gh-release:
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
type: feature
2-
title: Add bundle-time private link sanitization and changelog directive link visibility
2+
title: Add bundle-time link allowlist for PR and issue references
33
products:
44
- product: docs-builder
55
target: 0.100.0
@@ -9,7 +9,8 @@ areas:
99
prs:
1010
- https://github.com/elastic/docs-builder/pull/3002
1111
description: |
12-
Adds opt-in `bundle.sanitize_private_links` in changelog configuration (and per-profile override),
13-
with `--sanitize-private-links` and `--no-sanitize-private-links` on option-based changelog bundle.
14-
Rewrites PR/issue references targeting private `assembler.yml` repos to quoted `# PRIVATE` sentinels
15-
when resolve is true. The `{changelog}` directive gains `:link-visibility:` (`auto`, `keep-links`, `hide-links`).
12+
Replaces `bundle.sanitize_private_links` with explicit `bundle.link_allow_repos` (`owner/repo` list).
13+
When set (including an empty list), PR/issue references not in the allowlist are rewritten to quoted
14+
`# PRIVATE:` sentinels when the bundle is resolved. `bundle.repo` must be a single repository (no `+` syntax).
15+
Optional assembler.yml warnings when an allowlisted repo is missing or marked private.
16+
The `{changelog}` directive retains `:link-visibility:` (`auto`, `keep-links`, `hide-links`).

docs/cli/changelog/bundle.md

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,6 @@ The `--input-products` option determines which changelog files are gathered for
9797
: Each occurrence can be either comma-separated issues ( `--issues "https://github.com/owner/repo/issues/123,456"`) or a file path (for example `--issues /path/to/file.txt`).
9898
: When using a file, every line must be a fully-qualified GitHub issue URL such as `https://github.com/owner/repo/issues/123`. Bare numbers and short forms are not allowed in files.
9999

100-
`--no-sanitize-private-links`
101-
: Optional: Explicitly turn off the `sanitize_private_links` option if it's specified in the changelog configuration file.
102-
103100
`--no-resolve`
104101
: Optional: Explicitly turn off the `resolve` option if it's specified in the changelog configuration file.
105102

@@ -140,13 +137,6 @@ The `--input-products` option determines which changelog files are gathered for
140137
: Optional: Copy the contents of each changelog file into the entries array.
141138
: By default, the bundle contains only the file names and checksums.
142139

143-
`--sanitize-private-links`
144-
: Optional: Turn on [private link sanitization](#private-link-sanitization).
145-
: Pull requests and issues that target repositories marked `private: true` in the `references` section of `assembler.yml` are rewritten as quoted `# PRIVATE:` sentinel strings in the bundle file.
146-
: This option requires a resolved bundle: use `--resolve` or set `bundle.resolve: true` in the `changelog.yml`.
147-
: If sanitization is enabled and the bundle is not resolved, the command fails.
148-
: When you omit this option, it defaults to `bundle.sanitize_private_links` in your changelog configuration file, which defaults to `false`.
149-
150140
## Output files
151141

152142
Both modes use the same ordered fallback to determine where to write the bundle. The first value that is set wins:
@@ -284,27 +274,26 @@ rules:
284274
- "Monitoring"
285275
```
286276

287-
## Private link sanitization [private-link-sanitization]
277+
## PR and issue link allowlist [link-allowlist]
288278

289-
A changelog in a public repository might contain links to pull requests or issues in private repositories.
290-
To prevent that information from appearing in the documentation, use `bundle.sanitize_private_links` in the changelog configuration file (or a product-specific profile override) or the `--sanitize-private-links` command option.
279+
A changelog in a public repository might contain links to pull requests or issues in repositories that should not appear in published documentation.
291280

292-
This feature relies on the [`assembler.yml`](/configure/site/content.md) file and the existence of `private: true` to determine which repo links should be sanitized.
293-
Every repository that appears in a PR or issue link must be listed under `assembler.yml` `references`. References to unknown repositories fail the command so you can fix the registry.
294-
Repos are assumed to be `private: false` unless you specify otherwise.
281+
Set `bundle.link_allow_repos` in `changelog.yml` to an explicit list of `owner/repo` strings (for example, `elastic/elasticsearch`). When this key is present (including as an empty list), PR and issue references are filtered at bundle time: only links whose resolved repository is in the list are kept; others are rewritten to quoted `# PRIVATE:` sentinel strings in the bundle YAML.
295282

296283
:::{important}
297-
When you use these options, you must also set `bundle.resolve: true` or specify `--resolve`.
298-
Unresolved bundles that only store `file:` pointers do not get this rewrite; if you need private link sanitization, you must use a resolved bundle.
284+
`bundle.link_allow_repos` requires a **resolved** bundle. Set `bundle.resolve: true` or pass `--resolve`. Unresolved bundles that only store `file:` pointers are not rewritten.
299285
:::
300286

301-
The `changelog bundle`, `changelog gh-release`, and `changelog bundle-amend` commands rewrite PR and issue references that **target** private repositories into quoted sentinel strings such as `"# PRIVATE: …"` in the bundle file.
302-
The changelog directive and `changelog render` command then omit these sentinels from the documentation.
287+
When [`assembler.yml`](/configure/site/content.md) is available, docs-builder emits **warnings** (non-fatal) if an allowlisted repo is missing from `references` or is marked `private: true`, so you can verify the registry before publishing.
288+
289+
The `changelog bundle`, `changelog gh-release`, and `changelog bundle-amend` commands apply the same rules. The changelog directive and `changelog render` command omit `# PRIVATE:` sentinels from rendered documentation.
303290

304291
:::{warning}
305292
Sentinel values are omitted from rendered documentation but remain in bundle files; they are not cryptographic redaction.
306293
:::
307294

295+
`bundle.repo` must name a **single** GitHub repository (do not use `repo1+repo2` merged-repo syntax).
296+
308297
## Option-based examples
309298

310299
### Bundle by report or URL list

docs/contribute/changelog.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -712,8 +712,8 @@ Top-level `bundle` fields:
712712
|---|---|
713713
| `repo` | Default GitHub repository name applied to all profiles. Falls back to product ID if not set at any level. |
714714
| `owner` | Default GitHub repository owner applied to all profiles. |
715-
| `resolve` | When `true`, embeds full changelog entry content in the bundle (same as `--resolve`). Required when `sanitize_private_links` is enabled. |
716-
| `sanitize_private_links` | When `true`, rewrites PR/issue references that target private repositories (per `assembler.yml` `references`) to quoted `# PRIVATE:` sentinel strings in bundle YAML. Requires `resolve: true` and a non-empty `references` section in `assembler.yml`. Default `false`. Refer to [Private link sanitization at bundle time](/cli/changelog/bundle.md#private-link-sanitization). |
715+
| `resolve` | When `true`, embeds full changelog entry content in the bundle (same as `--resolve`). Required when `link_allow_repos` is set. |
716+
| `link_allow_repos` | When set (including an empty list), only PR/issue links whose resolved repository is in this `owner/repo` list are kept; others are rewritten to `# PRIVATE:` sentinels in bundle YAML. When absent, no link filtering is applied. Requires `resolve: true`. Refer to [PR and issue link allowlist](/cli/changelog/bundle.md#link-allowlist). |
717717

718718
Profile configuration fields in `bundle.profiles`:
719719

@@ -726,7 +726,6 @@ Profile configuration fields in `bundle.profiles`:
726726
| `repo` | Optional. Overrides `bundle.repo` for this profile only. Required when `source: github_release` is used and no `bundle.repo` is set. |
727727
| `owner` | Optional. Overrides `bundle.owner` for this profile only. |
728728
| `hide_features` | List of feature IDs to embed in the bundle as hidden. |
729-
| `sanitize_private_links` | Optional. Overrides `bundle.sanitize_private_links` for this profile. |
730729

731730
Example profile configuration:
732731

@@ -1047,7 +1046,7 @@ The `--hide-features` option on the `render` command and the `hide-features` fie
10471046

10481047
A changelog can reference multiple pull requests and issues in the `prs` and `issues` array fields.
10491048

1050-
To comment out the private links in all changelogs in your bundles, refer to [changelog bundle](/cli/changelog/bundle.md#private-link-sanitization).
1049+
To comment out links that are not in your allowlist in all changelogs in your bundles, refer to [changelog bundle](/cli/changelog/bundle.md#link-allowlist).
10511050

10521051
If you are working in a private repo and do not want any pull request or issue links to appear (even if they target a public repo), you also have the option to configure link visibiblity in the [changelog directive](/syntax/changelog.md) and [changelog render](/cli/changelog/render.md) command.
10531052

@@ -1295,7 +1294,7 @@ docs-builder changelog remove elasticsearch-release 9.2.0 --dry-run
12951294
The command automatically discovers `changelog.yml` by checking `./changelog.yml` then `./docs/changelog.yml` relative to your current directory.
12961295
If no configuration file is found, the command returns an error with advice to create one or to run from the directory where the file exists.
12971296

1298-
The `output`, `output_products`, `hide_features`, `sanitize_private_links`, and `resolve` fields are bundle-specific and are always ignored for removal (along with other bundle-only settings that do not affect which changelog files match the filter).
1297+
The `output`, `output_products`, `hide_features`, `link_allow_repos`, and `resolve` fields are bundle-specific and are always ignored for removal (along with other bundle-only settings that do not affect which changelog files match the filter).
12991298
Which other fields are used depends on the profile type:
13001299

13011300
- Standard profiles: only the `products` field is used. The `repo` and `owner` fields are ignored (they only affect bundle output metadata).

src/Elastic.Documentation.Configuration/Changelog/BundleConfiguration.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,11 @@ public record BundleConfiguration
3939
public string? Owner { get; init; }
4040

4141
/// <summary>
42-
/// When true, PR/issue references targeting repositories marked <c>private: true</c> in
43-
/// <c>assembler.yml</c> are rewritten to sentinel values at bundle time (requires <see cref="Resolve"/>).
42+
/// When set (including an empty list), PR/issue references whose resolved <c>owner/repo</c> is not listed
43+
/// are rewritten to <c># PRIVATE:</c> sentinels at bundle time. When absent, no link filtering is applied.
44+
/// Requires <see cref="Resolve"/>.
4445
/// </summary>
45-
public bool SanitizePrivateLinks { get; init; }
46+
public IReadOnlyList<string>? LinkAllowRepos { get; init; }
4647

4748
/// <summary>
4849
/// Named bundle profiles for different release scenarios.
@@ -104,9 +105,4 @@ public record BundleProfile
104105
/// Mutually exclusive with <see cref="Products"/>.
105106
/// </summary>
106107
public string? Source { get; init; }
107-
108-
/// <summary>
109-
/// When set, overrides <see cref="BundleConfiguration.SanitizePrivateLinks"/> for this profile.
110-
/// </summary>
111-
public bool? SanitizePrivateLinks { get; init; }
112108
}

src/services/Elastic.Changelog/Bundling/ChangelogBundleAmendService.cs

Lines changed: 44 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -134,22 +134,14 @@ public async Task<bool> AmendBundle(IDiagnosticsCollector collector, AmendBundle
134134
if (_configLoader != null)
135135
changelogConfig = await _configLoader.LoadChangelogConfiguration(collector, null, ctx);
136136

137-
var sanitizePrivateLinks = changelogConfig?.Bundle?.SanitizePrivateLinks == true;
138-
Bundle? parentBundleForSanitize = null;
139-
AssemblyConfiguration? assemblyForSanitize = null;
137+
var linkAllowRepos = changelogConfig?.Bundle?.LinkAllowRepos;
138+
var linkAllowlistActive = linkAllowRepos != null;
139+
Bundle? parentBundleForAllowlist = null;
140140

141-
if (sanitizePrivateLinks)
141+
if (linkAllowlistActive)
142142
{
143-
if (configurationContext == null)
144-
{
145-
collector.EmitError(
146-
string.Empty,
147-
"Private link sanitization requires assembler configuration. Run docs-builder with a valid configuration context.");
148-
return false;
149-
}
150-
151143
if (parentBundleFromInfer != null)
152-
parentBundleForSanitize = parentBundleFromInfer;
144+
parentBundleForAllowlist = parentBundleFromInfer;
153145
else
154146
{
155147
var (ok, loaded) = await TryDeserializeParentBundleAsync(
@@ -160,61 +152,47 @@ public async Task<bool> AmendBundle(IDiagnosticsCollector collector, AmendBundle
160152
if (!ok)
161153
return false;
162154
ArgumentNullException.ThrowIfNull(loaded);
163-
parentBundleForSanitize = loaded;
155+
parentBundleForAllowlist = loaded;
164156
}
165157

166-
ArgumentNullException.ThrowIfNull(parentBundleForSanitize);
167-
if (!parentBundleForSanitize.IsResolved)
158+
ArgumentNullException.ThrowIfNull(parentBundleForAllowlist);
159+
if (!parentBundleForAllowlist.IsResolved)
168160
{
169161
collector.EmitError(
170162
string.Empty,
171-
"Private link sanitization requires the parent bundle to be resolved (inline entry content). " +
172-
"Re-create the bundle with resolve enabled, or disable bundle.sanitize_private_links.");
163+
"bundle.link_allow_repos requires the parent bundle to be resolved (inline entry content). " +
164+
"Re-create the bundle with resolve enabled, or remove bundle.link_allow_repos.");
173165
return false;
174166
}
175167

176-
var assemblyYaml = configurationContext.ConfigurationFileProvider.AssemblerFile.ReadToEnd();
177-
try
178-
{
179-
assemblyForSanitize = AssemblyConfiguration.Deserialize(assemblyYaml, skipPrivateRepositories: false);
180-
}
181-
catch (Exception ex) when (ex is not (OutOfMemoryException or StackOverflowException))
182-
{
183-
collector.EmitError(
184-
string.Empty,
185-
$"Failed to parse assembler configuration YAML: {ex.Message}",
186-
ex);
187-
return false;
188-
}
189-
190-
var owner = parentBundleForSanitize.Products.Count > 0 ? parentBundleForSanitize.Products[0].Owner ?? "elastic" : "elastic";
191-
var repo = parentBundleForSanitize.Products.Count > 0 ? parentBundleForSanitize.Products[0].Repo : null;
192-
if (!PrivateChangelogLinkSanitizer.TrySanitizeBundle(
168+
var owner = parentBundleForAllowlist.Products.Count > 0 ? parentBundleForAllowlist.Products[0].Owner ?? "elastic" : "elastic";
169+
var repo = parentBundleForAllowlist.Products.Count > 0 ? parentBundleForAllowlist.Products[0].Repo : null;
170+
if (!LinkAllowlistSanitizer.TryApplyBundle(
193171
collector,
194-
parentBundleForSanitize,
195-
assemblyForSanitize,
172+
parentBundleForAllowlist,
173+
linkAllowRepos!,
196174
owner,
197175
repo,
198176
out _,
199-
out var parentHadUnsanitizedLinks))
177+
out var parentHadAllowlistChanges))
200178
return false;
201179

202-
if (parentHadUnsanitizedLinks)
180+
if (parentHadAllowlistChanges)
203181
{
204182
collector.EmitError(
205183
string.Empty,
206-
"Private link sanitization requires the parent bundle to already reflect sanitized PR/issue references. " +
207-
"Re-create the parent bundle with bundle.sanitize_private_links enabled and resolve enabled, " +
208-
"or disable bundle.sanitize_private_links for amend.");
184+
"bundle.link_allow_repos requires the parent bundle to already reflect filtered PR/issue references. " +
185+
"Re-create the parent bundle with the same bundle.link_allow_repos and resolve enabled, " +
186+
"or remove bundle.link_allow_repos for amend.");
209187
return false;
210188
}
211189
}
212190

213-
if (sanitizePrivateLinks && !shouldResolve)
191+
if (linkAllowlistActive && !shouldResolve)
214192
{
215193
collector.EmitError(
216194
string.Empty,
217-
"Private link sanitization requires resolved amend content. Use --resolve or ensure the original bundle is resolved, or disable bundle.sanitize_private_links.");
195+
"bundle.link_allow_repos requires resolved amend content. Use --resolve or ensure the original bundle is resolved, or remove bundle.link_allow_repos.");
218196
return false;
219197
}
220198

@@ -236,23 +214,38 @@ public async Task<bool> AmendBundle(IDiagnosticsCollector collector, AmendBundle
236214
};
237215

238216
var bundleForWrite = amendBundle;
239-
if (sanitizePrivateLinks && shouldResolve)
217+
if (linkAllowlistActive && shouldResolve)
240218
{
241-
ArgumentNullException.ThrowIfNull(parentBundleForSanitize);
242-
ArgumentNullException.ThrowIfNull(assemblyForSanitize);
243-
var owner = parentBundleForSanitize.Products.Count > 0 ? parentBundleForSanitize.Products[0].Owner ?? "elastic" : "elastic";
244-
var repo = parentBundleForSanitize.Products.Count > 0 ? parentBundleForSanitize.Products[0].Repo : null;
219+
ArgumentNullException.ThrowIfNull(parentBundleForAllowlist);
220+
var owner = parentBundleForAllowlist.Products.Count > 0 ? parentBundleForAllowlist.Products[0].Owner ?? "elastic" : "elastic";
221+
var repo = parentBundleForAllowlist.Products.Count > 0 ? parentBundleForAllowlist.Products[0].Repo : null;
245222

246-
if (!PrivateChangelogLinkSanitizer.TrySanitizeBundle(
223+
if (!LinkAllowlistSanitizer.TryApplyBundle(
247224
collector,
248225
amendBundle,
249-
assemblyForSanitize,
226+
linkAllowRepos!,
250227
owner,
251228
repo,
252229
out var sanitized,
253230
out _))
254231
return false;
255232
bundleForWrite = sanitized;
233+
234+
if (configurationContext != null && linkAllowRepos!.Count > 0)
235+
{
236+
try
237+
{
238+
var assemblyYaml = configurationContext.ConfigurationFileProvider.AssemblerFile.ReadToEnd();
239+
var assembly = AssemblyConfiguration.Deserialize(assemblyYaml, skipPrivateRepositories: false);
240+
LinkAllowlistSanitizer.EmitAssemblerDiagnostics(collector, linkAllowRepos!, assembly);
241+
}
242+
catch (Exception ex) when (ex is not (OutOfMemoryException or StackOverflowException))
243+
{
244+
collector.EmitWarning(
245+
string.Empty,
246+
$"Could not load assembler.yml for bundle.link_allow_repos diagnostics: {ex.Message}");
247+
}
248+
}
256249
}
257250

258251
// Serialize and write the amend file

0 commit comments

Comments
 (0)