@redpanda-data/docs-extensions-and-macros 5.55.0 → 5.56.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 +315 -7
- package/cli-utils/docs-links-to-xrefs.js +174 -0
- package/cli-utils/generated-marker.js +43 -0
- package/cli-utils/helm-commented-values.js +37 -3
- package/extension-utils/llms-utils.js +6 -2
- package/extension-utils/metadata-candidates.js +304 -0
- package/extension-utils/metadata-check.js +174 -0
- package/extension-utils/metadata-suggest.js +573 -0
- package/extension-utils/taxonomy-categories.js +106 -0
- package/extension-utils/taxonomy.js +566 -0
- package/extensions/REFERENCE.adoc +90 -4
- package/extensions/algolia-indexer/generate-index.js +13 -1
- package/extensions/algolia-indexer/index.js +4 -2
- package/extensions/convert-sitemap-to-markdown.js +47 -12
- package/extensions/convert-to-markdown.js +176 -0
- package/extensions/generate-fields-only-pages.js +14 -0
- package/extensions/modify-connect-tag-playbook.js +2 -2
- package/extensions/url-to-xref.js +92 -22
- package/extensions/validate-attributes.js +10 -1
- package/package.json +2 -1
- package/tools/check-metadata/index.js +523 -0
- package/tools/cloud-regions/cloud-regions-table-adoc.hbs +1 -2
- package/tools/cloud-regions/cloud-regions-table-md.hbs +2 -2
- package/tools/cloud-regions/render-cloud-regions.js +3 -0
- package/tools/dispatch-graph/check-dispatch-graph.js +790 -0
- package/tools/dispatch-graph/classify-runs.js +128 -0
- package/tools/dispatch-graph/parse-workflows.js +499 -0
- package/tools/enterprise-features/detect-gating-changes.js +393 -0
- package/tools/enterprise-features/verify.js +2 -1
- package/tools/gen-rpk-ascii.py +7 -1
- package/tools/lint-strings/fixtures/connect/lint_connect.go +8 -0
- package/tools/lint-strings/fixtures/crd/lint_types.go +4 -1
- package/tools/lint-strings/fixtures/rpk-pr461/delete.base.go +42 -0
- package/tools/lint-strings/fixtures/rpk-pr461/delete.head.go +45 -0
- package/tools/lint-strings/fixtures/rpk-pr461/reviewer-comments.json +1 -0
- package/tools/lint-strings/index.js +133 -3
- package/tools/lint-strings/rules/verbatim-asciidoc.js +166 -1
- package/tools/lint-strings/surfaces/connect.js +72 -12
- package/tools/lint-strings/surfaces/crd.js +10 -6
- package/tools/metrics/metrics.py +11 -0
- package/tools/property-extractor/generate-handlebars-docs.js +9 -1
- package/tools/property-extractor/helpers/applyPropertyLinks.js +35 -0
- package/tools/redpanda-connect/generate-rpcn-connector-docs.js +29 -8
- package/tools/redpanda-connect/rpcn-connector-docs-handler.js +19 -24
- package/tools/redpanda-connect/templates/bloblang-function.hbs +1 -1
- package/tools/redpanda-connect/templates/descriptions-partials.hbs +1 -1
- package/tools/redpanda-connect/templates/examples-partials.hbs +1 -1
- package/tools/redpanda-connect/templates/fields-partials.hbs +2 -2
- package/tools/redpanda-connect/templates/metadata-partials.hbs +1 -1
- package/tools/rpk-docs/generate-rpk-docs.js +2 -0
- package/tools/rpk-docs/generate-x-env-partial.js +7 -4
- package/tools/rpk-docs/helpers/index.js +4 -1
- package/tools/rpk-docs/templates/command.hbs +1 -1
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Finds enterprise gating changes in a Redpanda core diff that no doc string
|
|
5
|
+
* announces, and shapes them as doc-impact findings.
|
|
6
|
+
*
|
|
7
|
+
* The doc-strings review derives its published-content impact from the doc
|
|
8
|
+
* strings a PR changes. Two kinds of change carry no doc string at all and
|
|
9
|
+
* still change what the licensing docs say:
|
|
10
|
+
*
|
|
11
|
+
* - a value added to (or removed from) `enum class license_required_feature`
|
|
12
|
+
* in src/v/features/enterprise_features.h: the feature starts (or stops)
|
|
13
|
+
* being reported by `rpk cluster license info` and
|
|
14
|
+
* GET /v1/features/enterprise, and the enterprise features registry in
|
|
15
|
+
* redpanda-data/docs needs an entry under an approved external name;
|
|
16
|
+
* - a property wrapped in `config::enterprise<>` in
|
|
17
|
+
* src/v/config/configuration.h: the property becomes (or stops being)
|
|
18
|
+
* license-gated, so the licensing tables and the disable page change;
|
|
19
|
+
* - a changed restricted value of an enterprise<> property in
|
|
20
|
+
* src/v/config/configuration.cc: which values need a license changes,
|
|
21
|
+
* with no change to the header or to any doc string.
|
|
22
|
+
*
|
|
23
|
+
* streaming-enterprise PR 579 (2026-10-07) was the first miss: it added
|
|
24
|
+
* `manual_finalization` to the enum and changed license enforcement, touched
|
|
25
|
+
* no doc string, and so filed nothing. This module reads the diff text only,
|
|
26
|
+
* so a test can hand it a saved patch; the CLI wrapper runs git.
|
|
27
|
+
*
|
|
28
|
+
* Detection is by added and removed lines, paired by name, so a value that
|
|
29
|
+
* merely moves within the enum is not reported. Both signals the header
|
|
30
|
+
* carries for an enum value are used (the member line inside the enum, and
|
|
31
|
+
* the `case license_required_feature::<name>:` line in format_to), so a
|
|
32
|
+
* hunk header that happens not to name the enum still detects.
|
|
33
|
+
*
|
|
34
|
+
* Each finding names ONE affected page, the licensing overview. The docs-site
|
|
35
|
+
* receiver falls back to matching a finding with no name match against open
|
|
36
|
+
* tickets that share at least two page topics, so two gating findings that
|
|
37
|
+
* both listed the overview and the disable page would be treated as one
|
|
38
|
+
* ticket however different the features. The disable page is named in the
|
|
39
|
+
* summary text instead.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
const ENUM_HEADER = 'src/v/features/enterprise_features.h'
|
|
43
|
+
const CONFIG_HEADER = 'src/v/config/configuration.h'
|
|
44
|
+
const CONFIG_SOURCE = 'src/v/config/configuration.cc'
|
|
45
|
+
|
|
46
|
+
const LICENSING_OVERVIEW = 'https://docs.redpanda.com/streaming/current/get-started/licensing/overview/'
|
|
47
|
+
const DISABLE_PAGE = 'https://docs.redpanda.com/streaming/current/get-started/licensing/disable-enterprise-features/'
|
|
48
|
+
const REGISTRY_URL = 'https://github.com/redpanda-data/docs/blob/main/shared/modules/ROOT/partials/enterprise-features.yml'
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Split unified diff text into { path, lines[] } per file, where lines are
|
|
52
|
+
* the raw hunk lines (prefixed with ' ', '+', '-') and hunk headers.
|
|
53
|
+
*/
|
|
54
|
+
function splitFiles (diffText) {
|
|
55
|
+
const files = []
|
|
56
|
+
let current = null
|
|
57
|
+
for (const raw of String(diffText || '').split('\n')) {
|
|
58
|
+
const m = /^diff --git a\/(.+?) b\/(.+)$/.exec(raw)
|
|
59
|
+
if (m) {
|
|
60
|
+
current = { path: m[2], lines: [] }
|
|
61
|
+
files.push(current)
|
|
62
|
+
continue
|
|
63
|
+
}
|
|
64
|
+
if (!current) continue
|
|
65
|
+
if (/^(\+\+\+|---|index |new file|deleted file|similarity|rename|old mode|new mode)/.test(raw)) continue
|
|
66
|
+
current.lines.push(raw)
|
|
67
|
+
}
|
|
68
|
+
return files
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Enum values added and removed in enterprise_features.h.
|
|
73
|
+
* @returns {{added: string[], removed: string[]}}
|
|
74
|
+
*/
|
|
75
|
+
function enumChanges (lines) {
|
|
76
|
+
const added = new Set()
|
|
77
|
+
const removed = new Set()
|
|
78
|
+
// The member line only counts inside the enum. Track that from the hunk
|
|
79
|
+
// header and from context lines, both of which git carries in the diff.
|
|
80
|
+
// Only enum members count: `case license_required_feature::x:` labels move
|
|
81
|
+
// whenever the formatter is rewritten (26.2 replaced operator<< with a
|
|
82
|
+
// format_to switch), so a case label is never read as a feature change.
|
|
83
|
+
let inEnum = false
|
|
84
|
+
for (const line of lines) {
|
|
85
|
+
if (line.startsWith('@@')) {
|
|
86
|
+
inEnum = /enum class license_required_feature/.test(line)
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
const body = line.slice(1)
|
|
90
|
+
if (/^\s*enum class license_required_feature\b/.test(body)) { inEnum = true; continue }
|
|
91
|
+
if (inEnum && /^\s*};/.test(body)) { inEnum = false; continue }
|
|
92
|
+
const sign = line[0]
|
|
93
|
+
if (sign !== '+' && sign !== '-') continue
|
|
94
|
+
const target = sign === '+' ? added : removed
|
|
95
|
+
const member = inEnum && /^\s*([a-z][a-z0-9_]*)\s*,?\s*(\/\/.*)?$/.exec(body)
|
|
96
|
+
if (member) target.add(member[1])
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
added: [...added].filter((n) => !removed.has(n)).sort(),
|
|
100
|
+
removed: [...removed].filter((n) => !added.has(n)).sort()
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// One declaration can span lines (clang-format wraps a long template
|
|
105
|
+
// argument list, so `enterprise<...>` and the member name land on separate
|
|
106
|
+
// lines). Lines are joined into statements before matching: a statement ends
|
|
107
|
+
// at a `;`, and also at a brace, an access specifier or a preprocessor line,
|
|
108
|
+
// so a declaration never absorbs the line before it.
|
|
109
|
+
const ENTERPRISE_DECL = /^\s*(?:config::)?enterprise<[\s\S]*>\s*([a-z][a-z0-9_]*)\s*;\s*$/
|
|
110
|
+
|
|
111
|
+
// Comments and attributes are dropped before joining: a `/* ... */` or
|
|
112
|
+
// `[[attr]]` line above a declaration has no `;` or brace, so it would
|
|
113
|
+
// otherwise be glued to the front of the declaration and hide it.
|
|
114
|
+
function stripComments (text, state) {
|
|
115
|
+
let out = ''
|
|
116
|
+
let rest = text
|
|
117
|
+
// A hunk can start inside a block comment whose opener is above it; a
|
|
118
|
+
// line that starts with `*` or `*/` is read as part of that comment.
|
|
119
|
+
if (!state.inBlock && /^\s*\*(\s|\/|$)/.test(rest)) state.inBlock = true
|
|
120
|
+
while (rest) {
|
|
121
|
+
if (state.inBlock) {
|
|
122
|
+
const end = rest.indexOf('*/')
|
|
123
|
+
if (end === -1) return out
|
|
124
|
+
state.inBlock = false
|
|
125
|
+
rest = rest.slice(end + 2)
|
|
126
|
+
continue
|
|
127
|
+
}
|
|
128
|
+
const line = rest.indexOf('//')
|
|
129
|
+
const block = rest.indexOf('/*')
|
|
130
|
+
if (block !== -1 && (line === -1 || block < line)) {
|
|
131
|
+
out += rest.slice(0, block) + ' '
|
|
132
|
+
state.inBlock = true
|
|
133
|
+
rest = rest.slice(block + 2)
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
136
|
+
out += line === -1 ? rest : rest.slice(0, line)
|
|
137
|
+
break
|
|
138
|
+
}
|
|
139
|
+
return out.replace(/\[\[[^\]]*\]\]/g, ' ')
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Join C++ source lines into statements. Each statement carries whether any
|
|
144
|
+
* of its lines is marked `changed`.
|
|
145
|
+
* @param {Array<{text: string, changed?: boolean}>} lines
|
|
146
|
+
* @returns {Array<{text: string, changed: boolean}>}
|
|
147
|
+
*/
|
|
148
|
+
function joinStatements (lines) {
|
|
149
|
+
const out = []
|
|
150
|
+
let buf = []
|
|
151
|
+
let changed = false
|
|
152
|
+
const flush = () => {
|
|
153
|
+
if (buf.length) out.push({ text: buf.join(' '), changed })
|
|
154
|
+
buf = []
|
|
155
|
+
changed = false
|
|
156
|
+
}
|
|
157
|
+
const state = { inBlock: false }
|
|
158
|
+
for (const { text, changed: c } of lines) {
|
|
159
|
+
const body = stripComments(text, state).trim()
|
|
160
|
+
if (!body) continue
|
|
161
|
+
if (/^#/.test(body) || /^(public|private|protected)\s*:$/.test(body)) { flush(); continue }
|
|
162
|
+
buf.push(body)
|
|
163
|
+
changed = changed || Boolean(c)
|
|
164
|
+
if (/[;{}]$/.test(body)) flush()
|
|
165
|
+
}
|
|
166
|
+
flush()
|
|
167
|
+
return out
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Names of every property wrapped in enterprise<> in a configuration.h text.
|
|
172
|
+
* @param {string} headerText
|
|
173
|
+
* @returns {string[]}
|
|
174
|
+
*/
|
|
175
|
+
function enterprisePropertyNames (headerText) {
|
|
176
|
+
const names = new Set()
|
|
177
|
+
for (const st of joinStatements(String(headerText || '').split('\n').map((text) => ({ text })))) {
|
|
178
|
+
const m = ENTERPRISE_DECL.exec(st.text)
|
|
179
|
+
if (m) names.add(m[1])
|
|
180
|
+
}
|
|
181
|
+
return [...names].sort()
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Properties whose declaration gained or lost the enterprise<> wrapper in
|
|
186
|
+
* configuration.h. A declaration changed in place shows as a -/+ pair with
|
|
187
|
+
* the same name; the wrapper is on exactly one side. Each side of each hunk
|
|
188
|
+
* (context plus its own changed lines) is joined into statements, so a
|
|
189
|
+
* declaration that wraps across lines is read whole.
|
|
190
|
+
* @returns {{added: string[], removed: string[]}}
|
|
191
|
+
*/
|
|
192
|
+
function enterprisePropertyChanges (lines) {
|
|
193
|
+
const added = new Set()
|
|
194
|
+
const removed = new Set()
|
|
195
|
+
const collect = (side, target) => {
|
|
196
|
+
for (const st of joinStatements(side)) {
|
|
197
|
+
if (!st.changed) continue
|
|
198
|
+
const m = ENTERPRISE_DECL.exec(st.text)
|
|
199
|
+
if (m) target.add(m[1])
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
let oldSide = []
|
|
203
|
+
let newSide = []
|
|
204
|
+
const flushHunk = () => {
|
|
205
|
+
collect(oldSide, removed)
|
|
206
|
+
collect(newSide, added)
|
|
207
|
+
oldSide = []
|
|
208
|
+
newSide = []
|
|
209
|
+
}
|
|
210
|
+
for (const line of lines) {
|
|
211
|
+
if (line.startsWith('@@')) { flushHunk(); continue }
|
|
212
|
+
const sign = line[0]
|
|
213
|
+
const text = line.slice(1)
|
|
214
|
+
if (sign === '-') oldSide.push({ text, changed: true })
|
|
215
|
+
else if (sign === '+') newSide.push({ text, changed: true })
|
|
216
|
+
else if (sign === ' ') { oldSide.push({ text }); newSide.push({ text }) }
|
|
217
|
+
}
|
|
218
|
+
flushHunk()
|
|
219
|
+
return {
|
|
220
|
+
added: [...added].filter((n) => !removed.has(n)).sort(),
|
|
221
|
+
removed: [...removed].filter((n) => !added.has(n)).sort()
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Enterprise properties whose restricted value changed in configuration.cc.
|
|
227
|
+
* An enterprise<> property's initializer takes its restricted value (or a
|
|
228
|
+
* predicate) between `*this` and the property's name literal:
|
|
229
|
+
*
|
|
230
|
+
* , audit_enabled(
|
|
231
|
+
* *this,
|
|
232
|
+
* true, // restricted value
|
|
233
|
+
* "audit_enabled",
|
|
234
|
+
*
|
|
235
|
+
* so a changed line between a `*this,` line and the `"<name>",` line of a
|
|
236
|
+
* property in `enterpriseNames` changes which values need a license. Only
|
|
237
|
+
* the initializer is read: a restricted value that names a constant or a
|
|
238
|
+
* function defined elsewhere (sasl_mechanisms reads
|
|
239
|
+
* enterprise_sasl_mechanisms) changes where that is defined, and is not
|
|
240
|
+
* seen here.
|
|
241
|
+
* @param {string[]} lines - hunk lines of configuration.cc
|
|
242
|
+
* @param {Set<string>} enterpriseNames
|
|
243
|
+
* @returns {string[]}
|
|
244
|
+
*/
|
|
245
|
+
function restrictedValueChanges (lines, enterpriseNames) {
|
|
246
|
+
const found = new Set()
|
|
247
|
+
let init = null
|
|
248
|
+
let changed = false
|
|
249
|
+
for (const line of lines) {
|
|
250
|
+
if (line.startsWith('@@')) { init = null; changed = false; continue }
|
|
251
|
+
const sign = line[0]
|
|
252
|
+
if (sign !== ' ' && sign !== '+' && sign !== '-') continue
|
|
253
|
+
const body = line.slice(1)
|
|
254
|
+
const opens = /^\s*,\s*([a-z][a-z0-9_]*)\s*\(/.exec(body)
|
|
255
|
+
if (opens) { init = opens[1]; changed = false; continue }
|
|
256
|
+
if (/^\s*\*this\s*,/.test(body)) { changed = sign !== ' '; continue }
|
|
257
|
+
// The name literal closes the restricted arguments. A string literal
|
|
258
|
+
// that is neither this initializer's name nor an enterprise property
|
|
259
|
+
// (http_authentication's restricted "OIDC") is a value, not the name.
|
|
260
|
+
const lit = /^\s*"([a-z][a-z0-9_]*)"\s*,/.exec(body)
|
|
261
|
+
if (lit && (lit[1] === init || enterpriseNames.has(lit[1]))) {
|
|
262
|
+
if (changed && enterpriseNames.has(lit[1])) found.add(lit[1])
|
|
263
|
+
changed = false
|
|
264
|
+
continue
|
|
265
|
+
}
|
|
266
|
+
// A hunk that starts after `*this` still counts the changed lines up to
|
|
267
|
+
// the name literal its context carries.
|
|
268
|
+
if (sign !== ' ') changed = true
|
|
269
|
+
}
|
|
270
|
+
return [...found].sort()
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Detect enterprise gating changes in a diff.
|
|
275
|
+
* @param {string} diffText - unified diff (`git diff base...HEAD`)
|
|
276
|
+
* @param {object} [options]
|
|
277
|
+
* @param {string[]} [options.enterpriseNames] - properties wrapped in
|
|
278
|
+
* enterprise<> at HEAD (see enterprisePropertyNames). Without it, a
|
|
279
|
+
* configuration.cc change cannot be tied to an enterprise property and is
|
|
280
|
+
* not reported.
|
|
281
|
+
* @returns {Array<{surface: string, name: string, change_kind: string, affected_pages: string[], summary: string}>}
|
|
282
|
+
*/
|
|
283
|
+
function detectGatingChanges (diffText, { enterpriseNames = [] } = {}) {
|
|
284
|
+
const findings = []
|
|
285
|
+
const files = splitFiles(diffText)
|
|
286
|
+
const header = files.find((f) => f.path === CONFIG_HEADER)
|
|
287
|
+
const headerChanged = new Set(header ? Object.values(enterprisePropertyChanges(header.lines)).flat() : [])
|
|
288
|
+
for (const file of files) {
|
|
289
|
+
if (file.path === ENUM_HEADER) {
|
|
290
|
+
const { added, removed } = enumChanges(file.lines)
|
|
291
|
+
for (const name of added) {
|
|
292
|
+
findings.push({
|
|
293
|
+
surface: 'enterprise-feature',
|
|
294
|
+
name,
|
|
295
|
+
change_kind: 'enterprise-feature-added',
|
|
296
|
+
affected_pages: [LICENSING_OVERVIEW],
|
|
297
|
+
summary: `\`${name}\` was added to \`license_required_feature\` in \`${ENUM_HEADER}\`. From the first release that contains this change, \`rpk cluster license info\` and \`GET /v1/features/enterprise\` report it and the license nag names it. The enterprise features registry (${REGISTRY_URL}) needs an entry under the approved external name, with a gating property and, where a property turns it off, a row on ${DISABLE_PAGE}; the weekly registry check fails until it has one. Any page that describes this feature's license behavior needs the new reporting and enforcement.`
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
for (const name of removed) {
|
|
301
|
+
findings.push({
|
|
302
|
+
surface: 'enterprise-feature',
|
|
303
|
+
name,
|
|
304
|
+
change_kind: 'enterprise-feature-removed',
|
|
305
|
+
affected_pages: [LICENSING_OVERVIEW],
|
|
306
|
+
summary: `\`${name}\` was removed from \`license_required_feature\` in \`${ENUM_HEADER}\`. The release that contains this change no longer reports it as an enterprise feature. The enterprise features registry entry that points at it (${REGISTRY_URL}) needs a new source pointer or a note, never deletion: a published external name stays in the registry.`
|
|
307
|
+
})
|
|
308
|
+
}
|
|
309
|
+
} else if (file.path === CONFIG_HEADER) {
|
|
310
|
+
const { added, removed } = enterprisePropertyChanges(file.lines)
|
|
311
|
+
for (const name of added) {
|
|
312
|
+
findings.push({
|
|
313
|
+
surface: 'enterprise-property',
|
|
314
|
+
name,
|
|
315
|
+
change_kind: 'enterprise-gate-added',
|
|
316
|
+
affected_pages: [LICENSING_OVERVIEW],
|
|
317
|
+
summary: `\`${name}\` is now wrapped in \`config::enterprise<>\` in \`${CONFIG_HEADER}\`, so a restricted value needs a license from the first release that contains this change. The enterprise features registry (${REGISTRY_URL}) needs an entry that names this property as its source or gating property, and ${DISABLE_PAGE} needs a row for the unrestricted value.`
|
|
318
|
+
})
|
|
319
|
+
}
|
|
320
|
+
for (const name of removed) {
|
|
321
|
+
findings.push({
|
|
322
|
+
surface: 'enterprise-property',
|
|
323
|
+
name,
|
|
324
|
+
change_kind: 'enterprise-gate-removed',
|
|
325
|
+
affected_pages: [LICENSING_OVERVIEW],
|
|
326
|
+
summary: `\`${name}\` is no longer wrapped in \`config::enterprise<>\` in \`${CONFIG_HEADER}\`, so it no longer needs a license. The registry entry that names it (${REGISTRY_URL}) and its row on ${DISABLE_PAGE} need to change.`
|
|
327
|
+
})
|
|
328
|
+
}
|
|
329
|
+
} else if (file.path === CONFIG_SOURCE) {
|
|
330
|
+
// A property whose wrapper changed in the header is already reported
|
|
331
|
+
// above; its initializer changes with it.
|
|
332
|
+
for (const name of restrictedValueChanges(file.lines, new Set(enterpriseNames))) {
|
|
333
|
+
if (headerChanged.has(name)) continue
|
|
334
|
+
findings.push({
|
|
335
|
+
surface: 'enterprise-property',
|
|
336
|
+
name,
|
|
337
|
+
change_kind: 'enterprise-restricted-value-changed',
|
|
338
|
+
affected_pages: [LICENSING_OVERVIEW],
|
|
339
|
+
summary: `The restricted value of \`${name}\`, a \`config::enterprise<>\` property, changed in \`${CONFIG_SOURCE}\`, so which values need a license changes from the first release that contains this change. The enterprise features registry entry that names it (${REGISTRY_URL}) and its row on ${DISABLE_PAGE} need the new restricted and unrestricted values.`
|
|
340
|
+
})
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return findings
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Shape findings as the doc-impact report the review workflow dispatches
|
|
349
|
+
* (the same JSON the model writes to doc-impact.json), or null when there is
|
|
350
|
+
* nothing to report.
|
|
351
|
+
*/
|
|
352
|
+
function buildDocImpact (findings) {
|
|
353
|
+
if (!findings || findings.length === 0) return null
|
|
354
|
+
// The dispatch schema allows ten findings. Title, body and findings all
|
|
355
|
+
// come from the same capped list, so the ticket never names a change the
|
|
356
|
+
// findings (which the receiver dedupes on) leave out; the overflow is
|
|
357
|
+
// counted instead.
|
|
358
|
+
const capped = findings.slice(0, 10)
|
|
359
|
+
const overflow = findings.length - capped.length
|
|
360
|
+
const names = capped.map((f) => `\`${f.name}\``).join(', ')
|
|
361
|
+
const bullets = capped.map((f) => `- ${f.name} (${f.change_kind}): ${f.summary}`).join('\n')
|
|
362
|
+
return {
|
|
363
|
+
findings: capped,
|
|
364
|
+
proposed_ticket: {
|
|
365
|
+
title: `Update enterprise feature docs for ${capped.map((f) => f.name).join(', ')}${overflow > 0 ? ` and ${overflow} more` : ''}`,
|
|
366
|
+
body: [
|
|
367
|
+
`This PR changes which features Redpanda gates on an enterprise license, without changing any doc string, so the licensing docs do not pick it up on regeneration: ${names}.`,
|
|
368
|
+
'',
|
|
369
|
+
bullets,
|
|
370
|
+
...(overflow > 0 ? [`- ${overflow} more gating change(s) are not listed here; see the PR diff.`] : []),
|
|
371
|
+
'',
|
|
372
|
+
`Registry: ${REGISTRY_URL}`,
|
|
373
|
+
`Licensing overview: ${LICENSING_OVERVIEW}`,
|
|
374
|
+
`Disable page: ${DISABLE_PAGE}`,
|
|
375
|
+
'',
|
|
376
|
+
'The change is on the default branch until a release contains it; the docs change belongs to the version branch for that release. Confirm the affected release with the PR author.'
|
|
377
|
+
].join('\n')
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
module.exports = {
|
|
383
|
+
ENUM_HEADER,
|
|
384
|
+
CONFIG_HEADER,
|
|
385
|
+
CONFIG_SOURCE,
|
|
386
|
+
detectGatingChanges,
|
|
387
|
+
buildDocImpact,
|
|
388
|
+
enumChanges,
|
|
389
|
+
enterprisePropertyChanges,
|
|
390
|
+
enterprisePropertyNames,
|
|
391
|
+
restrictedValueChanges,
|
|
392
|
+
splitFiles
|
|
393
|
+
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
const yaml = require('js-yaml')
|
|
15
|
+
const { generatedMarker } = require('../../cli-utils/generated-marker')
|
|
15
16
|
const semver = require('semver')
|
|
16
17
|
|
|
17
18
|
// Mirrors macros/enterprise.js: no 'cloud' scope, because Redpanda Cloud has
|
|
@@ -197,7 +198,7 @@ function buildMappingPartial (features, enumValues) {
|
|
|
197
198
|
}
|
|
198
199
|
}
|
|
199
200
|
const lines = [
|
|
200
|
-
'
|
|
201
|
+
generatedMarker('validate enterprise-features --write-mapping', 'the enterprise feature names reported by rpk cluster license info'),
|
|
201
202
|
'// Maps the internal feature names reported by `rpk cluster license info`',
|
|
202
203
|
'// (enterprise_features_in_use) to their documented names.',
|
|
203
204
|
'[cols="1m,2a"]',
|
package/tools/gen-rpk-ascii.py
CHANGED
|
@@ -26,6 +26,11 @@ rpk_basic_command = ["rpk"]
|
|
|
26
26
|
|
|
27
27
|
def assert_period(s):return s if s.endswith('.') else s + '.'
|
|
28
28
|
|
|
29
|
+
# Same marker shape as cli-utils/generated-marker.js (no version, so a release
|
|
30
|
+
# does not rewrite every page); convert-to-markdown reads it.
|
|
31
|
+
GENERATED_MARKER = ("// This content is autogenerated. Do not edit manually. "
|
|
32
|
+
"Generated by doc-tools (rpk-docs) from the rpk command help in redpanda-data/streaming-enterprise.")
|
|
33
|
+
|
|
29
34
|
def escape_chars(s):
|
|
30
35
|
|
|
31
36
|
s = s.replace("./<timestamp>-bundle.zip", "./<timestamp>-bundle.zip")
|
|
@@ -311,7 +316,8 @@ def build_ascii(ascii_result, executed_command, explanation, usage, it_flags, fl
|
|
|
311
316
|
if not os.path.exists(rpk_gen_dir):
|
|
312
317
|
os.makedirs(rpk_gen_dir)
|
|
313
318
|
|
|
314
|
-
# Construct the asciidoc content
|
|
319
|
+
# Construct the asciidoc content, marker first
|
|
320
|
+
ascii_result += GENERATED_MARKER + "\n"
|
|
315
321
|
ascii_result += "= " + executed_command
|
|
316
322
|
ascii_result += "\n:description: " + executed_command
|
|
317
323
|
ascii_result += "\n\n" + explanation
|
|
@@ -23,6 +23,14 @@ When a consumer group is specified this input consumes one or more topics.
|
|
|
23
23
|
|
|
24
24
|
This fixture exercises the ` + "`raw` + interpreted + `raw`" + ` concatenation idiom.
|
|
25
25
|
|
|
26
|
+
ifndef::env-cloud[]
|
|
27
|
+
Set the consumer group in the input config so this paragraph publishes only to the self-managed docs.
|
|
28
|
+
endif::[]
|
|
29
|
+
|
|
30
|
+
ifdef::env-cloud[]
|
|
31
|
+
The consumer group is managed for you, so this paragraph publishes only to the Cloud docs.
|
|
32
|
+
endif::[]
|
|
33
|
+
|
|
26
34
|
== Metadata
|
|
27
35
|
|
|
28
36
|
This heading and the table below are legitimate AsciiDoc:
|
|
@@ -15,9 +15,12 @@ type WidgetSpec struct {
|
|
|
15
15
|
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ClusterSource is immutable"
|
|
16
16
|
ClusterSource *ClusterSource `json:"cluster"`
|
|
17
17
|
// Text is the actual unescaped text of a widget. The Go name matches
|
|
18
|
-
// the json name
|
|
18
|
+
// the json name up to case, and it still opens with the Go name.
|
|
19
19
|
// +required
|
|
20
20
|
Text string `json:"text,omitempty"`
|
|
21
|
+
// maxBytes is the largest widget payload accepted. It opens with the
|
|
22
|
+
// YAML key instead of what the field does.
|
|
23
|
+
MaxBytes int `json:"maxBytes,omitempty"`
|
|
21
24
|
// Specifies how many replicas to run. Conforming: describes behavior,
|
|
22
25
|
// never names the field.
|
|
23
26
|
// +kubebuilder:default=3
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Package lintfixture is a lint-strings test fixture: the help text of
|
|
2
|
+
// rpk topic delete before and after a change whose new wording came from a
|
|
3
|
+
// docs reviewer's comments. It only needs to parse as Go text for the
|
|
4
|
+
// scanner; it is never compiled.
|
|
5
|
+
package lintfixture
|
|
6
|
+
|
|
7
|
+
import (
|
|
8
|
+
"github.com/spf13/cobra"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
func newDeleteCommand() *cobra.Command {
|
|
12
|
+
var re bool
|
|
13
|
+
cmd := &cobra.Command{
|
|
14
|
+
Use: "delete [TOPICS...]",
|
|
15
|
+
Short: "Delete topics",
|
|
16
|
+
Long: `Delete topics.
|
|
17
|
+
|
|
18
|
+
This command deletes all requested topics, printing the success or fail status
|
|
19
|
+
per topic.
|
|
20
|
+
|
|
21
|
+
The --regex flag (-r) opts into parsing the input topics as regular expressions
|
|
22
|
+
and deleting any non-internal topic that matches any of expressions. The input
|
|
23
|
+
expressions are wrapped with ^ and $ so that the expression must match the
|
|
24
|
+
whole topic name (which also prevents accidental delete-everything mistakes).
|
|
25
|
+
|
|
26
|
+
The topic list command accepts the same input regex format as this delete
|
|
27
|
+
command. If you want to check what your regular expressions will delete before
|
|
28
|
+
actually deleting them, you can check the output of 'rpk topic list -r'.
|
|
29
|
+
|
|
30
|
+
For example,
|
|
31
|
+
|
|
32
|
+
delete foo bar # deletes topics foo and bar
|
|
33
|
+
delete -r '^f.*' '.*r$' # deletes any topic starting with f and any topics ending in r
|
|
34
|
+
delete -r '.*' # deletes all topics
|
|
35
|
+
delete -r . # deletes any one-character topics
|
|
36
|
+
|
|
37
|
+
`,
|
|
38
|
+
Run: func(cmd *cobra.Command, topics []string) {},
|
|
39
|
+
}
|
|
40
|
+
cmd.Flags().BoolVarP(&re, "regex", "r", false, "Parse topics as regex; delete any topic that matches any input topic expression")
|
|
41
|
+
return cmd
|
|
42
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Package lintfixture is a lint-strings test fixture: the help text of
|
|
2
|
+
// rpk topic delete before and after a change whose new wording came from a
|
|
3
|
+
// docs reviewer's comments. It only needs to parse as Go text for the
|
|
4
|
+
// scanner; it is never compiled.
|
|
5
|
+
package lintfixture
|
|
6
|
+
|
|
7
|
+
import (
|
|
8
|
+
"github.com/spf13/cobra"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
func newDeleteCommand() *cobra.Command {
|
|
12
|
+
var re bool
|
|
13
|
+
cmd := &cobra.Command{
|
|
14
|
+
Use: "delete [TOPICS...]",
|
|
15
|
+
Short: "Delete topics",
|
|
16
|
+
Long: `Delete topics.
|
|
17
|
+
|
|
18
|
+
This command deletes all requested topics, printing the success or fail status
|
|
19
|
+
per topic.
|
|
20
|
+
|
|
21
|
+
The --regex flag (-r) treats each argument as a regular expression and deletes
|
|
22
|
+
every topic whose full name matches any of them. rpk wraps each expression in
|
|
23
|
+
^ and $, so it must match the whole name.
|
|
24
|
+
|
|
25
|
+
Regex mode never deletes internal topics: the topics Redpanda reports as internal,
|
|
26
|
+
which rpk topic list shows only with -i. To delete an internal topic, name it
|
|
27
|
+
exactly, without -r.
|
|
28
|
+
|
|
29
|
+
The topic list command accepts the same input regex format as this delete
|
|
30
|
+
command. If you want to check what your regular expressions will delete before
|
|
31
|
+
actually deleting them, you can check the output of 'rpk topic list -r'.
|
|
32
|
+
|
|
33
|
+
For example,
|
|
34
|
+
|
|
35
|
+
delete foo bar # deletes topics foo and bar
|
|
36
|
+
delete -r '^f.*' '.*r$' # deletes any topic starting with f and any topics ending in r
|
|
37
|
+
delete -r '.*' # deletes all non-internal topics
|
|
38
|
+
delete -r . # deletes any one-character topics
|
|
39
|
+
|
|
40
|
+
`,
|
|
41
|
+
Run: func(cmd *cobra.Command, topics []string) {},
|
|
42
|
+
}
|
|
43
|
+
cmd.Flags().BoolVarP(&re, "regex", "r", false, "Treat the topic arguments as regular expressions and delete every matching topic except internal topics")
|
|
44
|
+
return cmd
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
["```suggestion\n\tcmd.Flags().BoolVarP(\u0026re, \"regex\", \"r\", false, \"Treat the topic arguments as regular expressions and delete every matching topic except internal topics\")\n```","Suggest replacing the `--regex` paragraph with:\n\nThe --regex flag (-r) treats each argument as a regular expression and deletes every topic whose full name matches any of them. rpk wraps each expression in ^ and $, so it must match the whole name.\n\nRegex mode never deletes internal topics: the topics Redpanda reports as internal, which 'rpk topic list' shows only with -i. To delete an internal topic, name it exactly, without -r."]
|