@redpanda-data/docs-extensions-and-macros 5.30.0 → 5.35.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-mcp.js +1 -1
- package/bin/doc-tools.js +5 -1
- package/bin/mcp-tools/lint-doc-strings.js +2 -1
- package/package.json +1 -1
- package/tools/lint-strings/diff.js +15 -1
- package/tools/lint-strings/fixtures/api/lint_admin_v2.proto +114 -0
- package/tools/lint-strings/fixtures/api/lint_api.proto +123 -0
- package/tools/lint-strings/fixtures/connect/lint_connect.go +1 -1
- package/tools/lint-strings/fixtures/crd/lint_types.go +70 -0
- package/tools/lint-strings/fixtures/helm/lint_values.yaml +16 -3
- package/tools/lint-strings/index.js +2 -1
- package/tools/lint-strings/rules/common.js +87 -1
- package/tools/lint-strings/surfaces/api.js +786 -0
- package/tools/lint-strings/surfaces/crd.js +204 -7
- package/tools/lint-strings/surfaces/helm.js +338 -12
- package/tools/property-extractor/Makefile +11 -0
- package/tools/property-extractor/merge-rp-util.js +208 -0
- package/tools/property-extractor/rp-util-fetch.js +447 -0
- package/tools/property-extractor/rp_util_merge.py +599 -0
- package/tools/property-extractor/templates/property.hbs +12 -5
- package/tools/property-extractor/templates/topic-property.hbs +7 -4
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require('child_process')
|
|
4
|
+
const fs = require('fs')
|
|
5
|
+
const os = require('os')
|
|
6
|
+
const path = require('path')
|
|
7
|
+
const { getGitHubToken } = require('../../cli-utils/github-token')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* rp_util's config schema JSON, for any streaming-enterprise ref (tag,
|
|
11
|
+
* branch, or SHA), built from source every time.
|
|
12
|
+
*
|
|
13
|
+
* This is the foundation, not a fallback: doc generation for work-in-progress
|
|
14
|
+
* branches depends on this working on its own, with no dependency on any
|
|
15
|
+
* pre-built artifact ever having been published for that ref. A pre-built
|
|
16
|
+
* cache in front of this (for tags) is a separate, optional speed layer --
|
|
17
|
+
* see the publish-rp-util-schema workflow -- and always falls through to
|
|
18
|
+
* this module when the cache misses.
|
|
19
|
+
*
|
|
20
|
+
* rp_util covers cluster scope (config::configuration) and everything that
|
|
21
|
+
* makes up broker scope: config::node_config plus the three standalone
|
|
22
|
+
* config_store classes that live alongside it (Pandaproxy, its Kafka
|
|
23
|
+
* client, and Schema Registry). Topic properties stay on the existing
|
|
24
|
+
* C++-source-parsing path (property_extractor.py) -- rp_util has no
|
|
25
|
+
* equivalent for them.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const STREAMING_ENTERPRISE_REPO = 'https://github.com/redpanda-data/streaming-enterprise.git'
|
|
29
|
+
const RP_UTIL_TARGET = '//src/v/rp_util:rp_util'
|
|
30
|
+
const RP_UTIL_BIN_RELPATH = path.join('bazel-bin', 'src', 'v', 'rp_util', 'rp_util')
|
|
31
|
+
// 24.04, not 22.04: Bazel's rules_foreign_cc bundles a prebuilt ninja binary
|
|
32
|
+
// requiring glibc >= 2.38, which Ubuntu 22.04 (glibc 2.35) doesn't have --
|
|
33
|
+
// confirmed via CMake's own "GLIBC_2.38 not found" failure trying to run it.
|
|
34
|
+
const DOCKER_LINUX_IMAGE = 'ubuntu:24.04'
|
|
35
|
+
// Named (not anonymous) volumes so Bazel's output/repository cache survives
|
|
36
|
+
// across separate `docker run --rm` invocations -- without this, every local
|
|
37
|
+
// macOS run pays a full cold Bazel build (boost, seastar, openssl, ...) under
|
|
38
|
+
// Rosetta emulation, which can take hours instead of minutes.
|
|
39
|
+
const DOCKER_BAZEL_CACHE_VOLUME = 'rp-util-bazel-cache'
|
|
40
|
+
const DOCKER_BAZELISK_CACHE_VOLUME = 'rp-util-bazelisk-cache'
|
|
41
|
+
|
|
42
|
+
// Every schema rp_util can dump, and the key each shows up under in
|
|
43
|
+
// getRpUtilSchema()'s return value. One flag per config_store class --
|
|
44
|
+
// see rp_util/main.cc.
|
|
45
|
+
const SCHEMA_FLAGS = [
|
|
46
|
+
{ key: 'clusterSchema', flag: '--config_schema_json' },
|
|
47
|
+
{ key: 'nodeSchema', flag: '--node_config_schema_json' },
|
|
48
|
+
{ key: 'pandaproxySchema', flag: '--pandaproxy_config_schema_json' },
|
|
49
|
+
{ key: 'kafkaClientSchema', flag: '--kafka_client_config_schema_json' },
|
|
50
|
+
{ key: 'schemaRegistrySchema', flag: '--schema_registry_config_schema_json' }
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Shallow-clone streaming-enterprise at a ref into destDir. Tries a
|
|
55
|
+
* branch/tag-name clone first (fast, matches almost every real call); a ref
|
|
56
|
+
* that isn't a branch or tag name at HEAD (a bare SHA, for example) falls
|
|
57
|
+
* back to a full clone plus checkout.
|
|
58
|
+
* @param {string} ref - Branch, tag, or commit SHA
|
|
59
|
+
* @param {string} destDir - Destination directory (must not exist yet)
|
|
60
|
+
*/
|
|
61
|
+
/**
|
|
62
|
+
* Strip a credential out of text before it's surfaced anywhere that gets
|
|
63
|
+
* logged -- an Error message ends up in plain CI logs. Covers a credential
|
|
64
|
+
* embedded in a Git remote URL's userinfo (e.g. https://<token>@github.com/...),
|
|
65
|
+
* which Git echoes back verbatim in common failures (a private repo it can't
|
|
66
|
+
* access, a bad ref, ...), and a Basic-auth header in case Git ever echoes
|
|
67
|
+
* failing config back -- defense in depth: this module keeps the token out
|
|
68
|
+
* of argv and URLs entirely (see gitAuthEnv).
|
|
69
|
+
*/
|
|
70
|
+
function redactCredentials(text) {
|
|
71
|
+
return String(text || '')
|
|
72
|
+
.replace(/\/\/[^/@\s]+@/g, '//***@')
|
|
73
|
+
.replace(/(authorization:\s*basic\s+)\S+/gi, '$1***')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Build the environment that authenticates git's github.com requests via a
|
|
78
|
+
* per-invocation credential helper, injected through GIT_CONFIG_* env vars.
|
|
79
|
+
* No token byte ever appears in argv (readable by any local process via
|
|
80
|
+
* `ps`/`/proc/<pid>/cmdline` for the full multi-minute clone) or in a URL
|
|
81
|
+
* (echoed verbatim by git on common failures), and nothing is written into
|
|
82
|
+
* the resulting clone's .git/config, where it would otherwise sit readable
|
|
83
|
+
* on disk for the entire downstream Bazel/Docker build. Same env-only
|
|
84
|
+
* pattern the property-docs Makefile migration uses.
|
|
85
|
+
* @param {string} token
|
|
86
|
+
* @returns {object} env for spawnSync
|
|
87
|
+
*/
|
|
88
|
+
function gitAuthEnv(token) {
|
|
89
|
+
return {
|
|
90
|
+
...process.env,
|
|
91
|
+
RP_UTIL_FETCH_GIT_TOKEN: token,
|
|
92
|
+
GIT_CONFIG_COUNT: '2',
|
|
93
|
+
// Clear any inherited helpers first so a system credential manager
|
|
94
|
+
// can't intercept (or prompt) before ours answers.
|
|
95
|
+
GIT_CONFIG_KEY_0: 'credential.helper',
|
|
96
|
+
GIT_CONFIG_VALUE_0: '',
|
|
97
|
+
GIT_CONFIG_KEY_1: 'credential.https://github.com.helper',
|
|
98
|
+
// The helper reads the token from its own environment at callback time;
|
|
99
|
+
// argv carries only this static, secret-free string.
|
|
100
|
+
GIT_CONFIG_VALUE_1: '!f() { echo "username=x-access-token"; echo "password=$RP_UTIL_FETCH_GIT_TOKEN"; }; f'
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function cloneStreamingEnterprise(ref, destDir) {
|
|
105
|
+
const token = getGitHubToken()
|
|
106
|
+
if (!token) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
'No GitHub token available to clone redpanda-data/streaming-enterprise (it is private).\n' +
|
|
109
|
+
'Set GITHUB_TOKEN, REDPANDA_GITHUB_TOKEN, ACTIONS_BOT_TOKEN, or GIT_CREDENTIALS.'
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
// Plain (unauthenticated) URL -- auth travels only via the per-invocation
|
|
113
|
+
// credential-helper env below, so it's never in argv and never written to
|
|
114
|
+
// destDir/.git/config.
|
|
115
|
+
const repoUrl = STREAMING_ENTERPRISE_REPO
|
|
116
|
+
const authEnv = gitAuthEnv(token)
|
|
117
|
+
|
|
118
|
+
console.log(`Cloning streaming-enterprise@${ref} into ${destDir}...`)
|
|
119
|
+
const shallow = spawnSync('git', [
|
|
120
|
+
'clone', '-q', '--depth', '1', '--branch', ref, repoUrl, destDir
|
|
121
|
+
], { encoding: 'utf8', timeout: 300000, env: authEnv })
|
|
122
|
+
|
|
123
|
+
if (shallow.status === 0) return
|
|
124
|
+
|
|
125
|
+
// Not a branch/tag name at HEAD -- full clone, then checkout the ref directly.
|
|
126
|
+
fs.rmSync(destDir, { recursive: true, force: true })
|
|
127
|
+
console.log(`'${ref}' is not a branch/tag at HEAD; cloning full history to resolve it...`)
|
|
128
|
+
const full = spawnSync('git', ['clone', '-q', repoUrl, destDir], {
|
|
129
|
+
encoding: 'utf8', timeout: 600000, env: authEnv
|
|
130
|
+
})
|
|
131
|
+
if (full.status !== 0) {
|
|
132
|
+
throw new Error(`Failed to clone streaming-enterprise: ${redactCredentials(full.stderr)}`)
|
|
133
|
+
}
|
|
134
|
+
const checkout = spawnSync('git', ['checkout', '-q', ref], {
|
|
135
|
+
cwd: destDir, encoding: 'utf8', timeout: 60000
|
|
136
|
+
})
|
|
137
|
+
if (checkout.status !== 0) {
|
|
138
|
+
throw new Error(`Failed to checkout ref '${ref}' in streaming-enterprise: ${redactCredentials(checkout.stderr)}`)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function checkDockerAvailable() {
|
|
143
|
+
const check = spawnSync('docker', ['--version'], { encoding: 'utf8', timeout: 5000 })
|
|
144
|
+
if (check.status !== 0) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
'Docker is required to build rp_util (a Linux binary) on ' +
|
|
147
|
+
`${os.platform()}, but was not found.\n` +
|
|
148
|
+
'Start Docker Desktop, or run this on a Linux host/CI runner instead ' +
|
|
149
|
+
'(no Docker needed there -- see publish-rp-util-schema.yaml).'
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Build //src/v/rp_util:rp_util natively via Bazel. Linux only -- the
|
|
156
|
+
* hermetic LLVM toolchain this repo's .bazelrc configures targets Linux, and
|
|
157
|
+
* Seastar-based binaries don't build natively on macOS/arm64.
|
|
158
|
+
* @param {string} sourceDir - streaming-enterprise checkout root
|
|
159
|
+
*/
|
|
160
|
+
function buildNative(sourceDir) {
|
|
161
|
+
const bazelCheck = spawnSync('bazel', ['--version'], { encoding: 'utf8', timeout: 15000 })
|
|
162
|
+
if (bazelCheck.status !== 0) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
'bazel (or bazelisk on PATH as `bazel`) is required but was not found.\n' +
|
|
165
|
+
'In CI, set this up with bazel-contrib/setup-bazel before calling doc-tools; ' +
|
|
166
|
+
'locally, install bazelisk: https://github.com/bazelbuild/bazelisk'
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
console.log('Building rp_util natively via Bazel...')
|
|
171
|
+
const build = spawnSync('bazel', ['build', '--lockfile_mode=off', RP_UTIL_TARGET], {
|
|
172
|
+
cwd: sourceDir, encoding: 'utf8', timeout: 1800000, maxBuffer: 50 * 1024 * 1024
|
|
173
|
+
})
|
|
174
|
+
if (build.status !== 0) {
|
|
175
|
+
throw new Error(`Failed to build rp_util: ${build.stderr}`)
|
|
176
|
+
}
|
|
177
|
+
return path.join(sourceDir, RP_UTIL_BIN_RELPATH)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Build //src/v/rp_util:rp_util inside a Linux Docker container, and run
|
|
182
|
+
* every schema dump inside that same container -- the binary itself is a
|
|
183
|
+
* Linux ELF that can't be exec'd directly on the host once the container
|
|
184
|
+
* exits, so unlike buildNative() this returns the parsed JSON directly
|
|
185
|
+
* rather than a binary path.
|
|
186
|
+
* @param {string} sourceDir - streaming-enterprise checkout root
|
|
187
|
+
* @returns {object} keyed by SCHEMA_FLAGS' `key`s
|
|
188
|
+
*/
|
|
189
|
+
function buildAndRunInDocker(sourceDir) {
|
|
190
|
+
checkDockerAvailable()
|
|
191
|
+
|
|
192
|
+
// Match the container's CPU architecture to the host's. Forcing
|
|
193
|
+
// linux/amd64 on an Apple Silicon host makes Docker emulate x86_64 via
|
|
194
|
+
// Rosetta for every single instruction in a from-source C++ build (boost,
|
|
195
|
+
// seastar, openssl, ...) -- observed to turn a ~10-20min native build into
|
|
196
|
+
// a multi-hour one. CI itself builds natively on arm64 hardware; there's
|
|
197
|
+
// no reason a local arm64 Mac shouldn't too.
|
|
198
|
+
const dockerArch = os.arch() === 'arm64' ? 'arm64' : 'amd64'
|
|
199
|
+
const dockerPlatform = `linux/${dockerArch}`
|
|
200
|
+
const bazeliskAsset = `bazelisk-linux-${dockerArch}`
|
|
201
|
+
|
|
202
|
+
console.log(`Building and running rp_util in a ${dockerPlatform} ${DOCKER_LINUX_IMAGE} container...`)
|
|
203
|
+
|
|
204
|
+
const binPath = RP_UTIL_BIN_RELPATH.split(path.sep).join('/')
|
|
205
|
+
const dumpCommands = SCHEMA_FLAGS
|
|
206
|
+
.map(({ key, flag }) => `&& ./${binPath} ${flag} > /tmp/${key}.json`)
|
|
207
|
+
.join(' ')
|
|
208
|
+
const copyCommand = `&& cp ${SCHEMA_FLAGS.map(({ key }) => `/tmp/${key}.json`).join(' ')} /work/`
|
|
209
|
+
|
|
210
|
+
const command = [
|
|
211
|
+
// libatomic1: the hermetic LLVM toolchain's clang binary is linked
|
|
212
|
+
// against libatomic.so.1, which a bare ubuntu:22.04 image doesn't ship.
|
|
213
|
+
// build-essential + the autotools set below: several rules_foreign_cc
|
|
214
|
+
// genrules (liburing, krb5, ...) shell out straight to configure+make
|
|
215
|
+
// on PATH rather than through Bazel's own toolchain resolution, so they
|
|
216
|
+
// need a real host toolchain regardless of the hermetic LLVM one also
|
|
217
|
+
// being present. This list is deliberately generous -- CI builds in a
|
|
218
|
+
// purpose-built image (vtools, not checked out here) that already has
|
|
219
|
+
// all of this, so there's no local Dockerfile to read the exact list
|
|
220
|
+
// off of; better to over-install once than hit another 20-30min
|
|
221
|
+
// round-trip per missing tool.
|
|
222
|
+
'apt-get update -qq && apt-get install -qq -y libatomic1 build-essential curl ca-certificates ' +
|
|
223
|
+
'perl bison flex python3 pkg-config m4 autoconf automake libtool gawk texinfo',
|
|
224
|
+
'&& curl -fsSL -o /usr/local/bin/bazel',
|
|
225
|
+
`https://github.com/bazelbuild/bazelisk/releases/latest/download/${bazeliskAsset}`,
|
|
226
|
+
'&& chmod +x /usr/local/bin/bazel',
|
|
227
|
+
`&& bazel --output_user_root=/bazel-cache build --lockfile_mode=off ${RP_UTIL_TARGET}`,
|
|
228
|
+
dumpCommands,
|
|
229
|
+
copyCommand
|
|
230
|
+
].join(' ')
|
|
231
|
+
|
|
232
|
+
const result = spawnSync('docker', [
|
|
233
|
+
'run', '--rm', '--platform', dockerPlatform,
|
|
234
|
+
'-v', `${sourceDir}:/work`,
|
|
235
|
+
'-v', `${DOCKER_BAZEL_CACHE_VOLUME}:/bazel-cache`,
|
|
236
|
+
'-v', `${DOCKER_BAZELISK_CACHE_VOLUME}:/root/.cache/bazelisk`,
|
|
237
|
+
'-w', '/work',
|
|
238
|
+
DOCKER_LINUX_IMAGE,
|
|
239
|
+
'bash', '-c', command
|
|
240
|
+
], { encoding: 'utf8', timeout: 10800000, maxBuffer: 50 * 1024 * 1024 })
|
|
241
|
+
|
|
242
|
+
if (result.status !== 0) {
|
|
243
|
+
throw new Error(`Failed to build/run rp_util in Docker: ${result.stderr}`)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const schemas = {}
|
|
247
|
+
for (const { key } of SCHEMA_FLAGS) {
|
|
248
|
+
schemas[key] = JSON.parse(fs.readFileSync(path.join(sourceDir, `${key}.json`), 'utf8'))
|
|
249
|
+
}
|
|
250
|
+
return schemas
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function runSchemaFlag(binaryPath, flag) {
|
|
254
|
+
const result = spawnSync(binaryPath, [flag], {
|
|
255
|
+
encoding: 'utf8', timeout: 60000, maxBuffer: 50 * 1024 * 1024
|
|
256
|
+
})
|
|
257
|
+
if (result.status !== 0) {
|
|
258
|
+
throw new Error(`rp_util ${flag} failed: ${result.stderr}`)
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
return JSON.parse(result.stdout)
|
|
262
|
+
} catch (err) {
|
|
263
|
+
throw new Error(`rp_util ${flag} did not print valid JSON: ${err.message}`)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Where publish-rp-util-schema.yaml uploads its release assets, and the
|
|
268
|
+
// mapping from each asset's filename back to the schema key rp_util_merge.py
|
|
269
|
+
// and this module's own callers key everything by.
|
|
270
|
+
const RP_UTIL_SCHEMA_RELEASE_REPO = 'redpanda-data/docs-extensions-and-macros'
|
|
271
|
+
const RELEASE_ASSET_TO_SCHEMA_KEY = {
|
|
272
|
+
'cluster-config-schema.json': 'clusterSchema',
|
|
273
|
+
'node-config-schema.json': 'nodeSchema',
|
|
274
|
+
'pandaproxy-config-schema.json': 'pandaproxySchema',
|
|
275
|
+
'kafka-client-config-schema.json': 'kafkaClientSchema',
|
|
276
|
+
'schema-registry-config-schema.json': 'schemaRegistrySchema'
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Fetch rp_util's schema from a previously published GitHub release
|
|
281
|
+
* (publish-rp-util-schema.yaml's rp-util-schema-<tag> release in this repo)
|
|
282
|
+
* instead of building rp_util from source. A real doc-generation run can't
|
|
283
|
+
* afford a from-source Bazel build (15-45 minutes observed) every time --
|
|
284
|
+
* this is the fast path that exists so it doesn't have to.
|
|
285
|
+
* @param {string} tag - The exact tag the release was published for (e.g. "v26.2.2")
|
|
286
|
+
* @returns {Promise<object|null>} Schemas keyed like SCHEMA_FLAGS, or null if
|
|
287
|
+
* no release exists for this tag (caller should fall back to building)
|
|
288
|
+
*/
|
|
289
|
+
async function fetchPublishedSchema(tag) {
|
|
290
|
+
const token = getGitHubToken()
|
|
291
|
+
const headers = token ? { Authorization: `Bearer ${token}` } : {}
|
|
292
|
+
|
|
293
|
+
const releaseResp = await fetch(
|
|
294
|
+
`https://api.github.com/repos/${RP_UTIL_SCHEMA_RELEASE_REPO}/releases/tags/rp-util-schema-${tag}`,
|
|
295
|
+
{ headers }
|
|
296
|
+
)
|
|
297
|
+
if (releaseResp.status === 404) return null
|
|
298
|
+
if (!releaseResp.ok) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
`Failed to look up published rp_util schema release for ${tag}: ` +
|
|
301
|
+
`${releaseResp.status} ${releaseResp.statusText}`
|
|
302
|
+
)
|
|
303
|
+
}
|
|
304
|
+
const release = await releaseResp.json()
|
|
305
|
+
|
|
306
|
+
// A published release must carry all five assets, each a real schema
|
|
307
|
+
// payload, or it is not usable. Accepting whatever subset happens to be
|
|
308
|
+
// there looks like success while every missing scope silently keeps its
|
|
309
|
+
// Tree-sitter data -- the exact class of quiet wrong answer this whole
|
|
310
|
+
// rp_util path exists to remove. The publisher itself cannot produce a
|
|
311
|
+
// partial published release (`gh release create` uploads to a draft and
|
|
312
|
+
// publishes only once every asset is up, and the /releases/tags endpoint
|
|
313
|
+
// used above never returns drafts), but a hand-made backfill release, a
|
|
314
|
+
// manually deleted asset, or a future change to the publisher all can, so
|
|
315
|
+
// validate here rather than trusting the producer.
|
|
316
|
+
const assetsByName = new Map()
|
|
317
|
+
for (const asset of release.assets || []) {
|
|
318
|
+
assetsByName.set(asset.name, asset)
|
|
319
|
+
}
|
|
320
|
+
const missing = Object.keys(RELEASE_ASSET_TO_SCHEMA_KEY).filter(name => !assetsByName.has(name))
|
|
321
|
+
if (missing.length) {
|
|
322
|
+
console.warn(
|
|
323
|
+
`Published rp_util schema release rp-util-schema-${tag} is incomplete ` +
|
|
324
|
+
`(missing: ${missing.join(', ')}); ignoring it.`
|
|
325
|
+
)
|
|
326
|
+
return null
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const schemas = {}
|
|
330
|
+
for (const [name, key] of Object.entries(RELEASE_ASSET_TO_SCHEMA_KEY)) {
|
|
331
|
+
const asset = assetsByName.get(name)
|
|
332
|
+
const assetResp = await fetch(asset.url, {
|
|
333
|
+
headers: { ...headers, Accept: 'application/octet-stream' }
|
|
334
|
+
})
|
|
335
|
+
if (!assetResp.ok) {
|
|
336
|
+
throw new Error(
|
|
337
|
+
`Failed to download published rp_util schema asset ${asset.name}: ${assetResp.status}`
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
let payload
|
|
341
|
+
try {
|
|
342
|
+
payload = await assetResp.json()
|
|
343
|
+
} catch (err) {
|
|
344
|
+
console.warn(
|
|
345
|
+
`Published rp_util schema asset ${name} in rp-util-schema-${tag} is not valid JSON ` +
|
|
346
|
+
`(${err.message}); ignoring the release.`
|
|
347
|
+
)
|
|
348
|
+
return null
|
|
349
|
+
}
|
|
350
|
+
// Every rp_util schema flag dumps a {"properties": {...}} object; an
|
|
351
|
+
// asset without one is a truncated or wrong-shaped upload, not a schema
|
|
352
|
+
// the merge can map.
|
|
353
|
+
if (!payload || typeof payload.properties !== 'object' || payload.properties === null) {
|
|
354
|
+
console.warn(
|
|
355
|
+
`Published rp_util schema asset ${name} in rp-util-schema-${tag} has no properties ` +
|
|
356
|
+
`object; ignoring the release.`
|
|
357
|
+
)
|
|
358
|
+
return null
|
|
359
|
+
}
|
|
360
|
+
schemas[key] = payload
|
|
361
|
+
}
|
|
362
|
+
return schemas
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Get every schema rp_util can dump (cluster, plus everything that makes up
|
|
367
|
+
* broker scope) for a streaming-enterprise ref.
|
|
368
|
+
*
|
|
369
|
+
* Tries a previously published release first (fast: a few small downloads)
|
|
370
|
+
* and only falls back to building rp_util from source (slow: a from-scratch
|
|
371
|
+
* Bazel build) when no published release exists for this exact ref, or when
|
|
372
|
+
* the caller explicitly asks to skip that check.
|
|
373
|
+
* @param {string} ref - Branch, tag, or commit SHA in redpanda-data/streaming-enterprise
|
|
374
|
+
* @param {object} [options]
|
|
375
|
+
* @param {boolean} [options.preferPublished] - Try fetchPublishedSchema(ref) first.
|
|
376
|
+
* Default true. Set false to always build from source (e.g. `ref` isn't a
|
|
377
|
+
* released tag, or the caller explicitly wants a from-source build).
|
|
378
|
+
* @param {string} [options.sourcePath] - Use an existing local streaming-enterprise
|
|
379
|
+
* checkout instead of cloning (caller is responsible for it being at `ref`).
|
|
380
|
+
* Implies building from source -- skips the published-release check.
|
|
381
|
+
* @param {boolean} [options.keepSource] - Don't delete a clone this function made
|
|
382
|
+
* @returns {Promise<{clusterSchema: object, nodeSchema: object, pandaproxySchema: object,
|
|
383
|
+
* kafkaClientSchema: object, schemaRegistrySchema: object, sourcePath: string|null}>}
|
|
384
|
+
*/
|
|
385
|
+
async function getRpUtilSchema(ref, options = {}) {
|
|
386
|
+
const { sourcePath, keepSource = false, preferPublished = true } = options
|
|
387
|
+
|
|
388
|
+
// streaming-enterprise release/RC tags are v-prefixed, and schema releases
|
|
389
|
+
// are named rp-util-schema-<v-prefixed tag>. A v-less release-shaped ref
|
|
390
|
+
// ("26.2.2") can neither be cloned nor have a schema release under its
|
|
391
|
+
// verbatim name, so normalize it here -- otherwise every layer downstream
|
|
392
|
+
// (release lookup, clone, the publish workflow an error message points at)
|
|
393
|
+
// fails or no-ops on a name that can never exist.
|
|
394
|
+
if (/^\d+\.\d+\.\d+(-rc\d+)?$/.test(ref)) {
|
|
395
|
+
ref = `v${ref}`
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (!sourcePath && preferPublished) {
|
|
399
|
+
const published = await fetchPublishedSchema(ref)
|
|
400
|
+
if (published) {
|
|
401
|
+
return { ...published, sourcePath: null }
|
|
402
|
+
}
|
|
403
|
+
console.log(`No published rp_util schema release for ${ref}; building from source...`)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
let sourceDir = sourcePath
|
|
407
|
+
let ownsClone = false
|
|
408
|
+
if (!sourceDir) {
|
|
409
|
+
sourceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rp-util-source-'))
|
|
410
|
+
ownsClone = true
|
|
411
|
+
try {
|
|
412
|
+
cloneStreamingEnterprise(ref, sourceDir)
|
|
413
|
+
} catch (err) {
|
|
414
|
+
fs.rmSync(sourceDir, { recursive: true, force: true })
|
|
415
|
+
throw err
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
try {
|
|
420
|
+
let schemas
|
|
421
|
+
if (os.platform() === 'linux') {
|
|
422
|
+
const binaryPath = buildNative(sourceDir)
|
|
423
|
+
schemas = {}
|
|
424
|
+
for (const { key, flag } of SCHEMA_FLAGS) {
|
|
425
|
+
schemas[key] = runSchemaFlag(binaryPath, flag)
|
|
426
|
+
}
|
|
427
|
+
} else {
|
|
428
|
+
schemas = buildAndRunInDocker(sourceDir)
|
|
429
|
+
}
|
|
430
|
+
return { ...schemas, sourcePath: sourceDir }
|
|
431
|
+
} finally {
|
|
432
|
+
if (ownsClone && !keepSource) {
|
|
433
|
+
fs.rmSync(sourceDir, { recursive: true, force: true })
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
module.exports = {
|
|
439
|
+
getRpUtilSchema,
|
|
440
|
+
SCHEMA_FLAGS,
|
|
441
|
+
// exported for testing
|
|
442
|
+
cloneStreamingEnterprise,
|
|
443
|
+
buildNative,
|
|
444
|
+
buildAndRunInDocker,
|
|
445
|
+
runSchemaFlag,
|
|
446
|
+
fetchPublishedSchema
|
|
447
|
+
}
|