@redpanda-data/docs-extensions-and-macros 5.7.1 → 5.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/doc-tools.js +183 -0
- package/extensions/convert-to-markdown.js +115 -0
- package/extensions/property-page-index.js +44 -0
- package/extensions/validate-attributes.js +1 -1
- package/macros/PROPERTY_AND_ENTERPRISE_REFERENCES.adoc +90 -0
- package/macros/README.adoc +2 -0
- package/macros/REFERENCE.adoc +167 -0
- package/macros/badge.js +35 -13
- package/macros/enterprise.js +390 -0
- package/macros/prop.js +491 -0
- package/package.json +6 -2
- package/tools/enterprise-features/parsers.js +134 -0
- package/tools/enterprise-features/verify.js +244 -0
- package/tools/generate-cli-docs.js +19 -1
- package/tools/migrate-property-refs.js +206 -0
- package/tools/property-extractor/generate-handlebars-docs.js +28 -1
- package/tools/property-extractor/property_extractor.py +34 -0
- package/tools/redpanda-connect/generate-rpcn-connector-docs.js +108 -2
- package/tools/redpanda-connect/templates/bloblang-functions-overview.hbs +2 -0
- package/tools/redpanda-connect/templates/bloblang-methods-overview.hbs +2 -0
- package/tools/redpanda-connect/templates/connector.hbs +13 -1
- package/tools/rpk-docs/generate-plugin-stubs.js +56 -9
- package/tools/rpk-docs/templates/command.hbs +23 -5
package/macros/REFERENCE.adoc
CHANGED
|
@@ -20,6 +20,8 @@ antora:
|
|
|
20
20
|
|
|
21
21
|
== config_ref
|
|
22
22
|
|
|
23
|
+
IMPORTANT: `config_ref` is deprecated. The <<prop>> macro supersedes all of its functionality: it validates names against the published property reference, discovers the target page dynamically instead of taking a manual path, powers hover tooltips, and its `helm-path=auto` attribute replaces `config_ref`'s Kubernetes prefixing with a per-property correct Helm values path (`config_ref` prefixed every property with `storage.tiered.config.`, mislabeling non-tiered properties). Migrate call sites with `doc-tools generate migrate-property-refs --config-refs`. The macro stays registered only because the generated property partials still emit it.
|
|
24
|
+
|
|
23
25
|
This inline macro is used to generate a reference to a configuration value in the Redpanda documentation. The macro's parameters allow for control over the generated reference's format and the type of output produced.
|
|
24
26
|
|
|
25
27
|
=== Usage
|
|
@@ -62,6 +64,171 @@ asciidoc:
|
|
|
62
64
|
- '@redpanda-data/docs-extensions-and-macros/macros/config-ref'
|
|
63
65
|
----
|
|
64
66
|
|
|
67
|
+
== enterprise
|
|
68
|
+
|
|
69
|
+
This inline macro marks a feature as an enterprise feature in prose. The feature name renders as a uniquely styled term with a tooltip explaining that the feature requires an Enterprise Edition license, and links to the feature's documentation or to the licensing page.
|
|
70
|
+
|
|
71
|
+
The macro validates its target against the enterprise features registry: `modules/ROOT/partials/enterprise-features.yml` in the `shared` component (the `shared/` folder of the docs repository). The registry is the canonical list of enterprise features and their approved external names. Targets are matched case-insensitively, including aliases, and the canonical name, feature page link, and tooltip come from the matching entry. A target that matches no entry is reported according to `enterprise-validate`. To mark a new feature as enterprise, add it to the registry first.
|
|
72
|
+
|
|
73
|
+
=== Usage
|
|
74
|
+
|
|
75
|
+
[,asciidoc]
|
|
76
|
+
----
|
|
77
|
+
enterprise:Continuous Data Balancing[]
|
|
78
|
+
enterprise:Tiered Storage[xref=manage:tiered-storage.adoc]
|
|
79
|
+
enterprise:Audit Logging[text=audit logging]
|
|
80
|
+
enterprise:Iceberg Topics[tooltip=Iceberg Topics requires an Enterprise Edition license and object storage.]
|
|
81
|
+
----
|
|
82
|
+
|
|
83
|
+
The macro target is the feature name, which can contain spaces. Named attributes:
|
|
84
|
+
|
|
85
|
+
xref::
|
|
86
|
+
Optional Antora resource ID of the feature's documentation page. Overrides the registry entry's link. When neither is set, the term links to the licensing page.
|
|
87
|
+
+
|
|
88
|
+
Registry entries can carry environment-specific pages: on pages with the `env-cloud` attribute the entry's `xref-cloud` is used, on pages with `env-kubernetes` the entry's `xref-kubernetes` is used, and otherwise the entry's default `xref` (the Linux or generic self-managed page). This mirrors the `config_ref` macro's environment awareness.
|
|
89
|
+
|
|
90
|
+
text::
|
|
91
|
+
Optional display text override. Use it when the sentence needs different casing or phrasing than the feature name.
|
|
92
|
+
|
|
93
|
+
tooltip::
|
|
94
|
+
Optional tooltip text override. Overrides the registry entry's `tooltip`. The default is `<feature> requires an Enterprise Edition license.`
|
|
95
|
+
|
|
96
|
+
=== Configuration options
|
|
97
|
+
|
|
98
|
+
enterprise-validate::
|
|
99
|
+
How to report a target that does not match any registry entry: `warn` (default) logs a build warning with the file path and close-match suggestions, `error` fails the build, and `off` disables validation. When no registry is available in the build, the macro renders without validation and logs a single warning.
|
|
100
|
+
|
|
101
|
+
enterprise-licensing-page::
|
|
102
|
+
Resource ID of the licensing page used when no `xref` attribute is given. The default is `get-started:licensing/overview.adoc`.
|
|
103
|
+
|
|
104
|
+
enterprise-feature-role::
|
|
105
|
+
CSS class applied to the wrapping span. The default is `enterprise-feature`. Style this class in the UI bundle to give enterprise terms a consistent visual treatment.
|
|
106
|
+
|
|
107
|
+
enterprise-tooltip::
|
|
108
|
+
Controls the tooltip attribute: `title` (default), `true` (renders `data-enterprise-tooltip` for UI-driven tooltips), any attribute name starting with `data-`, or `false` to disable tooltips.
|
|
109
|
+
|
|
110
|
+
enterprise-links::
|
|
111
|
+
Set to `false` to render the term without a link.
|
|
112
|
+
|
|
113
|
+
enterprise-beta-badge::
|
|
114
|
+
Set to `false` to suppress the beta badge in prose. Generated tables always show it. See <<beta-features>>.
|
|
115
|
+
|
|
116
|
+
[[beta-features]]
|
|
117
|
+
=== Beta features
|
|
118
|
+
|
|
119
|
+
Add `beta: true` to a registry entry to mark the feature as beta. The macro then renders a beta badge after the feature name, both in prose and in the generated `enterprise_features` tables, so one registry field keeps every reference consistent:
|
|
120
|
+
|
|
121
|
+
[,yaml]
|
|
122
|
+
----
|
|
123
|
+
- name: Stretch Clusters
|
|
124
|
+
scope: operator
|
|
125
|
+
beta: true
|
|
126
|
+
feature-suffix: (StretchCluster resource)
|
|
127
|
+
----
|
|
128
|
+
|
|
129
|
+
Add `beta-tooltip` to give the badge hover text.
|
|
130
|
+
|
|
131
|
+
Do not write a `badge:` macro call into `feature-suffix` to achieve this. The badge is emitted as an inline passthrough, so it renders whether or not the consuming playbook registers the `badge` macro, and it degrades to the text `(beta)` in Markdown. A hand-written macro call renders as literal source text in any build that does not register that macro.
|
|
132
|
+
|
|
133
|
+
=== Markdown output
|
|
134
|
+
|
|
135
|
+
In HTML, the enterprise signal is carried by the span's CSS class and its tooltip attribute. Both are attributes, so an HTML-to-Markdown conversion drops them and the term arrives as an ordinary feature name. To keep the signal for readers of the Markdown pages, including AI agents, the `convert-to-markdown` extension appends an explicit `(enterprise)` marker and preserves any link:
|
|
136
|
+
|
|
137
|
+
[,markdown]
|
|
138
|
+
----
|
|
139
|
+
Enable [Tiered Storage](../manage/tiered-storage.md) (enterprise) today.
|
|
140
|
+
----
|
|
141
|
+
|
|
142
|
+
A feature that is also marked `beta: true` gets one combined marker rather than two adjacent parentheticals:
|
|
143
|
+
|
|
144
|
+
[,markdown]
|
|
145
|
+
----
|
|
146
|
+
Enable [Stretch Clusters](../deploy/stretch-clusters.md) (enterprise, beta) today.
|
|
147
|
+
----
|
|
148
|
+
|
|
149
|
+
This applies to the inline macro. The tables from `enterprise_features` need no enterprise marker, because their expiration column already states the licensing behavior as text. A beta badge in a table converts to `(beta)` on its own.
|
|
150
|
+
|
|
151
|
+
=== Registration
|
|
152
|
+
|
|
153
|
+
[,yaml]
|
|
154
|
+
----
|
|
155
|
+
asciidoc:
|
|
156
|
+
extensions:
|
|
157
|
+
- '@redpanda-data/docs-extensions-and-macros/macros/enterprise'
|
|
158
|
+
----
|
|
159
|
+
|
|
160
|
+
== enterprise_features
|
|
161
|
+
|
|
162
|
+
This block macro renders the licensing feature table for one scope from the enterprise features registry (see <<enterprise>>). Because the table is generated, it can never drift from the registry.
|
|
163
|
+
|
|
164
|
+
=== Usage
|
|
165
|
+
|
|
166
|
+
[,asciidoc]
|
|
167
|
+
----
|
|
168
|
+
enterprise_features::redpanda[]
|
|
169
|
+
enterprise_features::connect[]
|
|
170
|
+
enterprise_features::operator[title=Operator features,heading=What stops working]
|
|
171
|
+
----
|
|
172
|
+
|
|
173
|
+
The target is the scope: `redpanda`, `console`, `connect`, `operator`, or `cloud`. Rows render alphabetically by feature name with three columns: the feature name (linked to its documentation page when the entry has an `xref` or `url`), the description, and the expiration or restriction behavior. Named attributes:
|
|
174
|
+
|
|
175
|
+
title::
|
|
176
|
+
Optional table title override. The default is scope-specific, for example `Enterprise features in Redpanda`.
|
|
177
|
+
|
|
178
|
+
heading::
|
|
179
|
+
Optional third-column heading override. The default is `Behavior Upon Expiration` for the `redpanda` scope and `Restrictions Without Valid License` for the other scopes.
|
|
180
|
+
|
|
181
|
+
=== Registration
|
|
182
|
+
|
|
183
|
+
Registered together with the `enterprise` inline macro (see <<enterprise>>).
|
|
184
|
+
|
|
185
|
+
== prop
|
|
186
|
+
|
|
187
|
+
This inline macro references a Redpanda configuration property in prose. The property renders as a marked `<code>` element (`property-ref` class plus a `data-property-name` attribute) that the docs UI decorates with a hover-documentation tooltip. Marking is opt-in: only properties referenced through this macro get tooltips, so ambiguous words such as `admin` or `rack` in unrelated contexts (Helm values, feature settings) are never decorated by mistake.
|
|
188
|
+
|
|
189
|
+
The macro validates its target against the published property reference: the newest `redpanda-properties-<tag>.json` attachment in the `reference` module of the build, the same data the tooltips fetch at runtime. To migrate existing plain-backtick property mentions, use `doc-tools generate migrate-property-refs`.
|
|
190
|
+
|
|
191
|
+
=== Usage
|
|
192
|
+
|
|
193
|
+
[,asciidoc]
|
|
194
|
+
----
|
|
195
|
+
prop:cloud_storage_enabled[]
|
|
196
|
+
prop:iceberg_enabled[link=true]
|
|
197
|
+
prop:retention_ms[link=true,page=properties/topic-properties]
|
|
198
|
+
prop:write_caching_default[text=write caching]
|
|
199
|
+
----
|
|
200
|
+
|
|
201
|
+
The macro target is the property name. Named attributes:
|
|
202
|
+
|
|
203
|
+
link::
|
|
204
|
+
Set to `true` to also link the property to its reference page. The page is discovered dynamically: the macro indexes which reference-module page in the current component documents each property, by scanning property headings in the partials each page includes (respecting include tag filters such as `tags=redpanda-cloud`). Links are component-relative, so a Cloud page links to the Cloud component's own property pages, and they keep working if properties are split across different pages in the future. When discovery finds nothing, the page falls back deterministically to the property's `config_scope` (`cluster`, `broker`, or `topic`). This supersedes the `config_ref` macro's linking for most uses.
|
|
205
|
+
|
|
206
|
+
page::
|
|
207
|
+
Optional reference-page override (module-relative, without `.adoc`), for example `properties/object-storage-properties`. Overrides discovery entirely.
|
|
208
|
+
|
|
209
|
+
helm-path::
|
|
210
|
+
Set to `auto` to display the property as its Helm values path on pages rendered with the `env-kubernetes` attribute, so single-sourced content reads correctly for both Linux and Kubernetes audiences. The path is derived per property: `storage.tiered.config.<name>` for tiered storage properties, `config.node.<name>` for broker properties, and `config.cluster.<name>` for other cluster properties. On pages without `env-kubernetes`, the plain property name renders. This supersedes the deprecated `config_ref` macro's prefixing.
|
|
211
|
+
|
|
212
|
+
text::
|
|
213
|
+
Optional display text override. Wins over the Helm path display.
|
|
214
|
+
|
|
215
|
+
=== Configuration options
|
|
216
|
+
|
|
217
|
+
property-validate::
|
|
218
|
+
How to report a target that does not match any published property: `warn` (default) logs a build warning with the file path and close-match suggestions, `error` fails the build, and `off` disables validation. When no property JSON is available in the build, the macro renders without validation and logs a single warning.
|
|
219
|
+
|
|
220
|
+
property-ref-role::
|
|
221
|
+
CSS class applied to the code element. The default is `property-ref`.
|
|
222
|
+
|
|
223
|
+
=== Registration
|
|
224
|
+
|
|
225
|
+
[,yaml]
|
|
226
|
+
----
|
|
227
|
+
asciidoc:
|
|
228
|
+
extensions:
|
|
229
|
+
- '@redpanda-data/docs-extensions-and-macros/macros/prop'
|
|
230
|
+
----
|
|
231
|
+
|
|
65
232
|
== glossterm
|
|
66
233
|
|
|
67
234
|
The `glossterm` inline macro provides a way to define and reference glossary terms in your AsciiDoc documents.
|
package/macros/badge.js
CHANGED
|
@@ -1,21 +1,43 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Build the HTML for a badge.
|
|
5
|
+
*
|
|
6
|
+
* Extracted from the macro body so other macros can render an identical badge
|
|
7
|
+
* without depending on the badge macro being registered in the consuming
|
|
8
|
+
* playbook. The enterprise macro uses it to render the beta badge for registry
|
|
9
|
+
* entries marked `beta: true`, which keeps one definition of the markup and
|
|
10
|
+
* the CSS class names.
|
|
11
|
+
*
|
|
12
|
+
* @param {object} opts
|
|
13
|
+
* @param {string} [opts.label] - Badge text.
|
|
14
|
+
* @param {string} [opts.size] - `large` renders without surrounding brackets.
|
|
15
|
+
* @param {string} [opts.tooltip] - Tooltip text, rendered as `data-tooltip`.
|
|
16
|
+
* @returns {string} Badge HTML.
|
|
17
|
+
*/
|
|
18
|
+
function buildBadgeHtml ({ label, size, tooltip } = {}) {
|
|
19
|
+
const text = label || 'label';
|
|
20
|
+
const className = `badge--${text.toLowerCase().replace(/\s+/g, '-')}`;
|
|
21
|
+
const isLarge = size === 'large';
|
|
22
|
+
const sizeClass = isLarge ? 'badge--large' : '';
|
|
23
|
+
const tooltipAttr = tooltip ? ` data-tooltip="${tooltip}"` : '';
|
|
24
|
+
|
|
25
|
+
// Add brackets if not large. The brackets matter beyond styling: they are
|
|
26
|
+
// real text content, so the label survives an HTML-to-Markdown conversion as
|
|
27
|
+
// "(beta)" rather than vanishing with the CSS class.
|
|
28
|
+
const renderedLabel = isLarge ? text : `(${text})`;
|
|
29
|
+
|
|
30
|
+
return `<span class="badge ${className} ${sizeClass}"${tooltipAttr}>${renderedLabel}</span>`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports.buildBadgeHtml = buildBadgeHtml;
|
|
34
|
+
|
|
3
35
|
module.exports.register = function (registry) {
|
|
4
36
|
registry.inlineMacro(function () {
|
|
5
37
|
const self = this;
|
|
6
38
|
self.named('badge');
|
|
7
|
-
self.process((parent, target, attrs) =>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const isLarge = attrs.size === 'large';
|
|
11
|
-
const sizeClass = isLarge ? 'badge--large' : '';
|
|
12
|
-
const tooltip = attrs.tooltip;
|
|
13
|
-
const tooltipAttr = tooltip ? ` data-tooltip="${tooltip}"` : '';
|
|
14
|
-
|
|
15
|
-
// Add brackets if not large
|
|
16
|
-
const renderedLabel = isLarge ? label : `(${label})`;
|
|
17
|
-
|
|
18
|
-
return `<span class="badge ${className} ${sizeClass}"${tooltipAttr}>${renderedLabel}</span>`;
|
|
19
|
-
});
|
|
39
|
+
self.process((parent, target, attrs) =>
|
|
40
|
+
buildBadgeHtml({ label: attrs.label, size: attrs.size, tooltip: attrs.tooltip })
|
|
41
|
+
);
|
|
20
42
|
});
|
|
21
43
|
};
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/* Macros for marking and listing enterprise features.
|
|
4
|
+
*
|
|
5
|
+
* Inline macro — mark a feature in prose:
|
|
6
|
+
*
|
|
7
|
+
* enterprise:Continuous Data Balancing[]
|
|
8
|
+
* enterprise:Tiered Storage[xref=manage:tiered-storage.adoc]
|
|
9
|
+
* enterprise:Audit Logging[text=audit logging]
|
|
10
|
+
* enterprise:Iceberg Topics[tooltip=Iceberg Topics requires an Enterprise Edition license and object storage.]
|
|
11
|
+
*
|
|
12
|
+
* The feature name renders as a uniquely styled term with a tooltip that
|
|
13
|
+
* explains the feature requires an Enterprise Edition license. The term
|
|
14
|
+
* links to the feature's documentation when an xref attribute is given,
|
|
15
|
+
* and otherwise to the licensing page, so readers can always reach an
|
|
16
|
+
* explanation of what having (or not having) a license means.
|
|
17
|
+
*
|
|
18
|
+
* Block macro — render the licensing feature table for one scope:
|
|
19
|
+
*
|
|
20
|
+
* enterprise_features::redpanda[]
|
|
21
|
+
* enterprise_features::connect[title=Enterprise features in Redpanda Connect]
|
|
22
|
+
*
|
|
23
|
+
* Registry validation:
|
|
24
|
+
*
|
|
25
|
+
* Both macros read the canonical enterprise features registry from the
|
|
26
|
+
* 'shared' component (modules/ROOT/partials/enterprise-features.yml in the
|
|
27
|
+
* shared folder of the docs repo). Inline targets are resolved against the
|
|
28
|
+
* registry case-insensitively, including aliases, and the canonical name,
|
|
29
|
+
* feature xref, and tooltip come from the matching entry. Unknown targets
|
|
30
|
+
* are reported according to the enterprise-validate attribute. When no
|
|
31
|
+
* registry is available (for example, outside an Antora build), the macros
|
|
32
|
+
* fall back to unvalidated rendering with a single warning.
|
|
33
|
+
*
|
|
34
|
+
* Document or site attributes:
|
|
35
|
+
*
|
|
36
|
+
* enterprise-validate 'warn' (default) to log unknown feature names,
|
|
37
|
+
* 'error' to fail the build, 'off' to disable
|
|
38
|
+
* validation.
|
|
39
|
+
* enterprise-licensing-page Resource ID of the licensing page used when
|
|
40
|
+
* no feature page is known
|
|
41
|
+
* (default: get-started:licensing/overview.adoc).
|
|
42
|
+
* enterprise-feature-role CSS class applied to the wrapping span
|
|
43
|
+
* (default: enterprise-feature).
|
|
44
|
+
* enterprise-tooltip 'title' (default), 'true' (renders
|
|
45
|
+
* data-enterprise-tooltip), any attribute name
|
|
46
|
+
* starting with 'data-', or 'false' to disable.
|
|
47
|
+
* enterprise-links 'true' (default) to render the term as a link.
|
|
48
|
+
*
|
|
49
|
+
* Example use in a playbook:
|
|
50
|
+
*
|
|
51
|
+
* asciidoc:
|
|
52
|
+
* extensions:
|
|
53
|
+
* - '@redpanda-data/docs-extensions-and-macros/macros/enterprise'
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
const yaml = require('js-yaml')
|
|
57
|
+
const chalk = require('chalk')
|
|
58
|
+
const { buildBadgeHtml } = require('./badge')
|
|
59
|
+
|
|
60
|
+
const $enterpriseRegistry = Symbol('$enterpriseRegistry')
|
|
61
|
+
|
|
62
|
+
const DEFAULT_LICENSING_PAGE = 'get-started:licensing/overview.adoc'
|
|
63
|
+
const DEFAULT_ROLE = 'enterprise-feature'
|
|
64
|
+
const BETA_LABEL = 'beta'
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Render the beta badge for a registry entry marked `beta: true`.
|
|
68
|
+
*
|
|
69
|
+
* The badge HTML is built directly rather than emitted as `badge:[...]`
|
|
70
|
+
* AsciiDoc, so it renders whether or not the consuming playbook registers the
|
|
71
|
+
* badge macro. Writing the macro call into a registry field used to be the only
|
|
72
|
+
* way to flag a beta feature, and it silently produced the literal text
|
|
73
|
+
* `badge::[label=beta]` in any build without that macro registered.
|
|
74
|
+
*
|
|
75
|
+
* @param {object} entry - Registry entry.
|
|
76
|
+
* @returns {string} Badge HTML, or an empty string when the entry is not beta.
|
|
77
|
+
*/
|
|
78
|
+
function buildBetaBadge (entry) {
|
|
79
|
+
if (!entry || entry.beta !== true) return ''
|
|
80
|
+
return buildBadgeHtml({
|
|
81
|
+
label: BETA_LABEL,
|
|
82
|
+
tooltip: entry['beta-tooltip'] || undefined,
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The badge for a registry entry, wrapped for use in generated AsciiDoc.
|
|
88
|
+
*
|
|
89
|
+
* The block macro returns AsciiDoc source that Asciidoctor then parses, so raw
|
|
90
|
+
* HTML placed in a table cell is escaped and the reader sees the markup as
|
|
91
|
+
* text. An inline passthrough emits it verbatim instead, and unlike a
|
|
92
|
+
* `badge:[...]` macro call it does not require the badge macro to be registered
|
|
93
|
+
* in the consuming playbook.
|
|
94
|
+
*
|
|
95
|
+
* @param {object} entry - Registry entry.
|
|
96
|
+
* @returns {string} Passthrough-wrapped badge, or an empty string.
|
|
97
|
+
*/
|
|
98
|
+
function buildBetaBadgeAsciiDoc (entry) {
|
|
99
|
+
const html = buildBetaBadge(entry)
|
|
100
|
+
return html ? `pass:[${html}]` : ''
|
|
101
|
+
}
|
|
102
|
+
const REGISTRY_FILENAME = 'enterprise-features.yml'
|
|
103
|
+
const VALID_SCOPES = ['redpanda', 'console', 'connect', 'operator', 'cloud']
|
|
104
|
+
|
|
105
|
+
const TABLE_TITLES = {
|
|
106
|
+
redpanda: 'Enterprise features in Redpanda',
|
|
107
|
+
console: 'Enterprise features in Redpanda Console',
|
|
108
|
+
connect: 'Enterprise features in Redpanda Connect',
|
|
109
|
+
operator: 'Enterprise features in the Redpanda Operator',
|
|
110
|
+
cloud: 'Enterprise features in Redpanda Cloud',
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const THIRD_COLUMN_HEADINGS = {
|
|
114
|
+
redpanda: 'Behavior Upon Expiration',
|
|
115
|
+
default: 'Restrictions Without Valid License',
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let warnedNoRegistry = false
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Parse the registry YAML into a lookup structure. Exported for unit testing.
|
|
122
|
+
*
|
|
123
|
+
* @param {string} source - Raw YAML from enterprise-features.yml.
|
|
124
|
+
* @param {string} origin - Human-readable location for error messages.
|
|
125
|
+
* @returns {{features: object[], lookup: Map<string, object>}}
|
|
126
|
+
*/
|
|
127
|
+
function parseRegistry (source, origin = REGISTRY_FILENAME) {
|
|
128
|
+
const data = yaml.load(source)
|
|
129
|
+
if (!data || !Array.isArray(data.features)) {
|
|
130
|
+
throw new Error(`Enterprise features registry ${origin} has no 'features' list.`)
|
|
131
|
+
}
|
|
132
|
+
const lookup = new Map()
|
|
133
|
+
const claim = (rawKey, feature, kind) => {
|
|
134
|
+
const key = rawKey.trim().toLowerCase()
|
|
135
|
+
if (!key) return
|
|
136
|
+
const existing = lookup.get(key)
|
|
137
|
+
if (existing && existing !== feature) {
|
|
138
|
+
throw new Error(`Duplicate enterprise feature ${kind} '${rawKey}' in ${origin}: used by both '${existing.name}' and '${feature.name}'.`)
|
|
139
|
+
}
|
|
140
|
+
lookup.set(key, feature)
|
|
141
|
+
}
|
|
142
|
+
for (const feature of data.features) {
|
|
143
|
+
if (!feature || !feature.name) {
|
|
144
|
+
throw new Error(`Enterprise features registry ${origin} has an entry without a name.`)
|
|
145
|
+
}
|
|
146
|
+
if (feature.scope && !VALID_SCOPES.includes(feature.scope)) {
|
|
147
|
+
throw new Error(`Enterprise feature '${feature.name}' in ${origin} has unknown scope '${feature.scope}'.`)
|
|
148
|
+
}
|
|
149
|
+
claim(feature.name, feature, 'name')
|
|
150
|
+
for (const alias of feature.aliases || []) claim(String(alias), feature, 'alias')
|
|
151
|
+
}
|
|
152
|
+
return { features: data.features, lookup }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Load and cache the registry from the shared component in the Antora
|
|
157
|
+
* content catalog. Returns undefined when no registry is available.
|
|
158
|
+
*/
|
|
159
|
+
function loadRegistry (config) {
|
|
160
|
+
const contentCatalog = config && config.contentCatalog
|
|
161
|
+
if (!contentCatalog) return undefined
|
|
162
|
+
if (contentCatalog[$enterpriseRegistry] !== undefined) return contentCatalog[$enterpriseRegistry] || undefined
|
|
163
|
+
let registry = null
|
|
164
|
+
const partials = contentCatalog.findBy({ component: 'shared', module: 'ROOT', family: 'partial' }) || []
|
|
165
|
+
const registryFile = partials.find((file) => file.path && file.path.endsWith(REGISTRY_FILENAME))
|
|
166
|
+
if (registryFile) {
|
|
167
|
+
registry = parseRegistry(registryFile.contents.toString(), registryFile.path)
|
|
168
|
+
}
|
|
169
|
+
// Cache null too, so a missing registry is only searched for once per build.
|
|
170
|
+
contentCatalog[$enterpriseRegistry] = registry
|
|
171
|
+
return registry || undefined
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function warnNoRegistry () {
|
|
175
|
+
if (warnedNoRegistry) return
|
|
176
|
+
warnedNoRegistry = true
|
|
177
|
+
console.warn(chalk.yellow(`Enterprise features registry (${REGISTRY_FILENAME} in the shared component) not found; enterprise: targets are not validated.`))
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Report an unknown feature name according to the enterprise-validate mode.
|
|
182
|
+
*/
|
|
183
|
+
function reportUnknownFeature ({ feature, mode, registry, filePath }) {
|
|
184
|
+
if (mode === 'off') return
|
|
185
|
+
const candidates = registry.features
|
|
186
|
+
.map((entry) => entry.name)
|
|
187
|
+
.filter((name) => name.toLowerCase().includes(feature.trim().toLowerCase().split(/\s+/)[0] || ''))
|
|
188
|
+
.slice(0, 3)
|
|
189
|
+
const hint = candidates.length ? ` Did you mean: ${candidates.join(', ')}?` : ''
|
|
190
|
+
const where = filePath ? ` in ${filePath}` : ''
|
|
191
|
+
const message = `enterprise:${feature}[] does not match any feature in the enterprise features registry${where}.${hint} Add the feature to ${REGISTRY_FILENAME} in the shared component first.`
|
|
192
|
+
if (mode === 'error') throw new Error(message)
|
|
193
|
+
console.warn(chalk.yellow(message))
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Pick the environment-appropriate feature page from a registry entry.
|
|
198
|
+
* Some features have separate documentation for Kubernetes, Linux
|
|
199
|
+
* (self-managed), and Redpanda Cloud. Mirrors the config_ref macro's
|
|
200
|
+
* environment-awareness: the env-cloud and env-kubernetes page attributes
|
|
201
|
+
* select the xref-cloud and xref-kubernetes registry fields, falling back
|
|
202
|
+
* to the default xref.
|
|
203
|
+
*
|
|
204
|
+
* @param {object} entry - Registry entry.
|
|
205
|
+
* @param {object} document - Asciidoctor document (for env attributes).
|
|
206
|
+
* @returns {string|undefined}
|
|
207
|
+
*/
|
|
208
|
+
function resolveEntryXref (entry, document) {
|
|
209
|
+
if (!entry) return undefined
|
|
210
|
+
if (document.getAttribute('env-cloud') !== undefined && entry['xref-cloud']) return entry['xref-cloud']
|
|
211
|
+
if (document.getAttribute('env-kubernetes') !== undefined && entry['xref-kubernetes']) return entry['xref-kubernetes']
|
|
212
|
+
return entry.xref
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Resolve the tooltip attribute name from the enterprise-tooltip document
|
|
217
|
+
* attribute. Mirrors the glossary macro's contract.
|
|
218
|
+
*
|
|
219
|
+
* @param {string|undefined} raw - Raw attribute value.
|
|
220
|
+
* @returns {string|undefined} Attribute name to emit, or undefined when disabled.
|
|
221
|
+
*/
|
|
222
|
+
function resolveTooltipAttribute (raw) {
|
|
223
|
+
if (raw === 'false') return undefined
|
|
224
|
+
if (raw === undefined || raw === 'title') return 'title'
|
|
225
|
+
if (raw === 'true') return 'data-enterprise-tooltip'
|
|
226
|
+
if (raw.startsWith('data-')) return raw
|
|
227
|
+
console.warn(`enterprise-tooltip attribute '${raw}' must be 'title', 'true', 'false', or start with 'data-'. Falling back to 'title'.`)
|
|
228
|
+
return 'title'
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Build the AsciiDoc content emitted for one inline macro instance. Exported
|
|
233
|
+
* for unit testing.
|
|
234
|
+
*
|
|
235
|
+
* @param {object} opts
|
|
236
|
+
* @param {string} opts.feature - Canonical feature name.
|
|
237
|
+
* @param {string} [opts.text] - Display text override.
|
|
238
|
+
* @param {string} [opts.xref] - Resource ID of the feature documentation.
|
|
239
|
+
* @param {string} [opts.url] - Absolute link used when no xref exists.
|
|
240
|
+
* @param {string} [opts.tooltip] - Tooltip text override.
|
|
241
|
+
* @param {string} opts.licensingPage - Resource ID of the licensing page.
|
|
242
|
+
* @param {string} opts.role - CSS class for the wrapping span.
|
|
243
|
+
* @param {string|undefined} opts.tooltipAttr - Tooltip attribute name, or undefined to omit.
|
|
244
|
+
* @param {boolean} opts.links - Whether to render a link.
|
|
245
|
+
* @returns {string}
|
|
246
|
+
*/
|
|
247
|
+
function buildEnterpriseContent ({ feature, text, xref, url, tooltip, licensingPage, role, tooltipAttr, links }) {
|
|
248
|
+
const display = text || feature
|
|
249
|
+
const tooltipText = tooltip || `${feature} requires an Enterprise Edition license.`
|
|
250
|
+
const escapedTooltip = tooltipText.replace(/"/g, '"')
|
|
251
|
+
const tooltipHtml = tooltipAttr ? ` ${tooltipAttr}="${escapedTooltip}"` : ''
|
|
252
|
+
let inner = display
|
|
253
|
+
if (links) {
|
|
254
|
+
inner = xref ? `xref:${xref}[${display}]` : (url ? `link:${url}[${display}]` : `xref:${licensingPage}[${display}]`)
|
|
255
|
+
}
|
|
256
|
+
return `<span class="${role}"${tooltipHtml}>${inner}</span>`
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Build the AsciiDoc source of the licensing feature table for one scope.
|
|
261
|
+
* Exported for unit testing.
|
|
262
|
+
*
|
|
263
|
+
* @param {object[]} features - All registry entries.
|
|
264
|
+
* @param {string} scope - Scope to render.
|
|
265
|
+
* @param {object} [opts]
|
|
266
|
+
* @param {string} [opts.title] - Table title override.
|
|
267
|
+
* @param {string} [opts.heading] - Third column heading override.
|
|
268
|
+
* @returns {string}
|
|
269
|
+
*/
|
|
270
|
+
function buildFeatureTable (features, scope, opts = {}) {
|
|
271
|
+
const rows = features
|
|
272
|
+
.filter((feature) => feature.scope === scope)
|
|
273
|
+
.sort((a, b) => a.name.localeCompare(b.name, 'en', { sensitivity: 'base' }))
|
|
274
|
+
const title = opts.title || TABLE_TITLES[scope]
|
|
275
|
+
const heading = opts.heading || THIRD_COLUMN_HEADINGS[scope] || THIRD_COLUMN_HEADINGS.default
|
|
276
|
+
const lines = [`.${title}`, '[cols="1a,2a,2a"]', '|===', `| Feature | Description | ${heading}`, '']
|
|
277
|
+
for (const feature of rows) {
|
|
278
|
+
let cell = feature.xref
|
|
279
|
+
? `xref:${feature.xref}[${feature.name}]`
|
|
280
|
+
: (feature.url ? `link:${feature.url}[${feature.name}]` : feature.name)
|
|
281
|
+
if (feature['feature-suffix']) cell += ` ${feature['feature-suffix']}`
|
|
282
|
+
const betaBadge = buildBetaBadgeAsciiDoc(feature)
|
|
283
|
+
if (betaBadge) cell += ` ${betaBadge}`
|
|
284
|
+
if (feature['show-gating-property'] && feature['gating-property']) cell += `\n(\`${feature['gating-property']}\`)`
|
|
285
|
+
lines.push(`| ${cell}`)
|
|
286
|
+
lines.push(`| ${(feature.description || '').trim()}`)
|
|
287
|
+
lines.push(`| ${(feature.expiration || '').trim()}`)
|
|
288
|
+
lines.push('')
|
|
289
|
+
}
|
|
290
|
+
lines.push('|===')
|
|
291
|
+
return lines.join('\n')
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function enterpriseInlineMacro (config) {
|
|
295
|
+
return function () {
|
|
296
|
+
const self = this
|
|
297
|
+
self.named('enterprise')
|
|
298
|
+
// Specifying the regexp allows spaces in the feature name.
|
|
299
|
+
self.$option('regexp', /enterprise:([^[]+)\[(|.*?[^\\])\]/)
|
|
300
|
+
self.process(function (parent, target, attributes) {
|
|
301
|
+
const document = parent.getDocument()
|
|
302
|
+
let registry
|
|
303
|
+
if (config && config.contentCatalog) {
|
|
304
|
+
registry = loadRegistry(config)
|
|
305
|
+
if (!registry) warnNoRegistry()
|
|
306
|
+
}
|
|
307
|
+
let feature = target
|
|
308
|
+
let entry
|
|
309
|
+
if (registry) {
|
|
310
|
+
entry = registry.lookup.get(target.trim().toLowerCase())
|
|
311
|
+
if (entry) {
|
|
312
|
+
feature = entry.name
|
|
313
|
+
} else {
|
|
314
|
+
reportUnknownFeature({
|
|
315
|
+
feature: target,
|
|
316
|
+
mode: document.getAttribute('enterprise-validate', 'warn'),
|
|
317
|
+
registry,
|
|
318
|
+
filePath: config && config.file && config.file.src && config.file.src.path,
|
|
319
|
+
})
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
let content = buildEnterpriseContent({
|
|
323
|
+
feature,
|
|
324
|
+
text: attributes.text,
|
|
325
|
+
xref: attributes.xref || resolveEntryXref(entry, document),
|
|
326
|
+
url: entry && entry.url,
|
|
327
|
+
tooltip: attributes.tooltip || (entry && entry.tooltip) || undefined,
|
|
328
|
+
licensingPage: document.getAttribute('enterprise-licensing-page', DEFAULT_LICENSING_PAGE),
|
|
329
|
+
role: document.getAttribute('enterprise-feature-role', DEFAULT_ROLE),
|
|
330
|
+
tooltipAttr: resolveTooltipAttribute(document.getAttribute('enterprise-tooltip')),
|
|
331
|
+
links: document.getAttribute('enterprise-links', 'true') === 'true',
|
|
332
|
+
})
|
|
333
|
+
// A feature marked beta in the registry is beta wherever it is
|
|
334
|
+
// referenced, so prose gets the same badge as the generated tables.
|
|
335
|
+
// Set enterprise-beta-badge to false to suppress it in prose only.
|
|
336
|
+
if (document.getAttribute('enterprise-beta-badge', 'true') === 'true') {
|
|
337
|
+
const betaBadge = buildBetaBadge(entry)
|
|
338
|
+
if (betaBadge) content += ` ${betaBadge}`
|
|
339
|
+
}
|
|
340
|
+
// The xref inside the span is resolved by the 'macros' substitution,
|
|
341
|
+
// the same mechanism the config_ref macro relies on.
|
|
342
|
+
return self.createInline(parent, 'quoted', content, { attributes: { subs: 'macros' } })
|
|
343
|
+
})
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function enterpriseFeaturesBlockMacro (config) {
|
|
348
|
+
return function () {
|
|
349
|
+
const self = this
|
|
350
|
+
self.named('enterprise_features')
|
|
351
|
+
self.process(function (parent, target, attributes) {
|
|
352
|
+
const scope = (target || attributes.scope || '').trim()
|
|
353
|
+
if (!VALID_SCOPES.includes(scope)) {
|
|
354
|
+
throw new Error(`enterprise_features::[] needs a scope of ${VALID_SCOPES.join(', ')} as its target, got '${scope}'.`)
|
|
355
|
+
}
|
|
356
|
+
const registry = config && config.contentCatalog ? loadRegistry(config) : undefined
|
|
357
|
+
if (!registry) {
|
|
358
|
+
warnNoRegistry()
|
|
359
|
+
return self.parseContent(parent, `WARNING: The enterprise features registry is unavailable, so the ${scope} feature table cannot be rendered.`)
|
|
360
|
+
}
|
|
361
|
+
const table = buildFeatureTable(registry.features, scope, { title: attributes.title, heading: attributes.heading })
|
|
362
|
+
// A block anchor before the macro call ([[my-id]]) is consumed into the
|
|
363
|
+
// macro's id attribute. Re-emit it so crossrefs to the table keep working.
|
|
364
|
+
const source = attributes.id ? `[[${attributes.id}]]\n${table}` : table
|
|
365
|
+
return self.parseContent(parent, source)
|
|
366
|
+
})
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function register (registry, config = {}) {
|
|
371
|
+
if (typeof registry.register === 'function') {
|
|
372
|
+
registry.register(function () {
|
|
373
|
+
this.inlineMacro(enterpriseInlineMacro(config))
|
|
374
|
+
this.blockMacro(enterpriseFeaturesBlockMacro(config))
|
|
375
|
+
})
|
|
376
|
+
} else if (typeof registry.inlineMacro === 'function') {
|
|
377
|
+
registry.inlineMacro(enterpriseInlineMacro(config))
|
|
378
|
+
if (typeof registry.blockMacro === 'function') registry.blockMacro(enterpriseFeaturesBlockMacro(config))
|
|
379
|
+
} else {
|
|
380
|
+
console.warn("no 'inlineMacro' method on alleged registry")
|
|
381
|
+
}
|
|
382
|
+
return registry
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
module.exports.register = register
|
|
386
|
+
module.exports.buildEnterpriseContent = buildEnterpriseContent
|
|
387
|
+
module.exports.buildFeatureTable = buildFeatureTable
|
|
388
|
+
module.exports.parseRegistry = parseRegistry
|
|
389
|
+
module.exports.resolveEntryXref = resolveEntryXref
|
|
390
|
+
module.exports.resolveTooltipAttribute = resolveTooltipAttribute
|