@redpanda-data/docs-extensions-and-macros 5.51.1 → 5.52.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 +4 -0
- package/bin/doc-tools.js +1 -0
- package/bin/mcp-tools/rpcn-docs.js +1 -0
- package/extensions/REFERENCE.adoc +19 -4
- package/extensions/generate-rp-connect-categories.js +76 -25
- package/extensions/generate-rp-connect-info.js +150 -40
- package/extensions/modify-connect-tag-playbook.js +183 -17
- package/extensions/util/connect-catalog.js +153 -0
- package/macros/rp-connect-components.js +23 -8
- package/package.json +1 -1
- package/tools/lint-strings/diff.js +4 -1
- package/tools/lint-strings/engine.js +5 -0
- package/tools/lint-strings/fixtures/connect/lint_connect.go +1 -1
- package/tools/lint-strings/fixtures/connect/repo/go.mod.txt +5 -0
- package/tools/lint-strings/fixtures/connect/repo/internal/cli/lint.go +19 -0
- package/tools/lint-strings/fixtures/connect/repo/internal/impl/azure/common.go +20 -0
- package/tools/lint-strings/fixtures/connect/repo/internal/impl/azure/docs.go +8 -0
- package/tools/lint-strings/fixtures/connect/repo/internal/impl/azure/input_thing.go +32 -0
- package/tools/lint-strings/fixtures/connect/repo/internal/impl/azure/output_thing.go +12 -0
- package/tools/lint-strings/fixtures/connect/repo/internal/impl/widget/bloblang.go +21 -0
- package/tools/lint-strings/fixtures/connect/repo/internal/impl/widget/scanner.go +12 -0
- package/tools/lint-strings/fixtures/connect/repo/internal/plugins/info.csv +3 -0
- package/tools/lint-strings/fixtures/connect/repo/internal/retries/retries.go +13 -0
- package/tools/lint-strings/go-index.js +1143 -0
- package/tools/lint-strings/index.js +28 -2
- package/tools/lint-strings/rules/common.js +10 -2
- package/tools/lint-strings/surfaces/connect.js +1484 -137
- package/tools/lint-strings/surfaces/rpk.js +1 -0
- package/tools/redpanda-connect/generate-rpcn-connector-docs.js +23 -13
- package/tools/redpanda-connect/report-delta.js +1 -0
- package/tools/redpanda-connect/rpcn-connector-docs-handler.js +19 -13
- package/tools/redpanda-connect/templates/connector.hbs +20 -2
- package/tools/rpk-docs/helpers/index.js +33 -36
|
@@ -1,29 +1,195 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// Sources the Redpanda Connect reference partials from the connect repo.
|
|
4
|
+
//
|
|
5
|
+
// A playbook lists the connect repo as a content source:
|
|
6
|
+
//
|
|
7
|
+
// - url: https://github.com/redpanda-data/connect
|
|
8
|
+
// tags: latest # replaced with the latest release tag at build time
|
|
9
|
+
// start_path: docs
|
|
10
|
+
//
|
|
11
|
+
// The extension does two things:
|
|
12
|
+
//
|
|
13
|
+
// 1. Pins the source to the latest connect release tag, so the docs never
|
|
14
|
+
// show fields from unreleased code on connect's main branch.
|
|
15
|
+
// 2. Filters what the source contributes: only the generated
|
|
16
|
+
// modules/components/partials and modules/components/examples trees of the
|
|
17
|
+
// `connect` component are kept, and any file another source (rp-connect-docs)
|
|
18
|
+
// already provides is dropped. Antora fails the build on a duplicate page or
|
|
19
|
+
// partial, so this makes it safe to add the source to a playbook before or
|
|
20
|
+
// after rp-connect-docs stops committing its own generated copies, and it
|
|
21
|
+
// ignores the full pages that older connect tags still carry.
|
|
22
|
+
//
|
|
23
|
+
// It also shares the tag it resolves (util/connect-catalog), so
|
|
24
|
+
// generate-rp-connect-info reads info.csv from the same ref as the reference
|
|
25
|
+
// content.
|
|
26
|
+
|
|
3
27
|
const { raiseListenerLimit } = require('./util/raise-listener-limit')
|
|
28
|
+
const getLatestConnectTag = require('./version-fetcher/get-latest-connect')
|
|
29
|
+
const { getGitHubApiToken } = require('../cli-utils/github-token')
|
|
30
|
+
const { isConnectSource, isConnectOrigin, githubRepoOf, setResolvedConnectRef } = require('./util/connect-catalog')
|
|
31
|
+
|
|
32
|
+
const OWNER = 'redpanda-data'
|
|
33
|
+
const REPO = 'connect'
|
|
34
|
+
const COMPONENT = 'connect'
|
|
35
|
+
const KEPT_PATHS = ['modules/components/partials/', 'modules/components/examples/']
|
|
36
|
+
|
|
37
|
+
// HTTPS, SSH (ssh://), and scp-style (git@host:owner/repo) URLs are all
|
|
38
|
+
// remote content sources in Antora.
|
|
39
|
+
function isRemote (url) {
|
|
40
|
+
return /^(https?|ssh):\/\//.test(url || '') || /^[\w.-]+@[\w.-]+:/.test(url || '')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Only the redpanda-data/connect remote resolves to its own latest release.
|
|
44
|
+
// A fork with the same repo name would otherwise be pinned to a tag looked up
|
|
45
|
+
// on redpanda-data/connect.
|
|
46
|
+
function isUpstreamConnect (url) {
|
|
47
|
+
const repo = githubRepoOf(url)
|
|
48
|
+
return !!repo && repo.owner === OWNER && repo.repo === REPO
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Removes user info from every URL in the text, so credentials embedded in a
|
|
52
|
+
// content-source URL never reach the build log.
|
|
53
|
+
function redact (text) {
|
|
54
|
+
return String(text || '').replace(/([a-z][a-z+.-]*:\/\/)[^@/\s]*@/gi, '$1')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Highest stable vX.Y.Z tag from `git ls-remote` output. Used when the GitHub
|
|
58
|
+
// API is unavailable, for example when an unauthenticated build is rate limited.
|
|
59
|
+
function highestStableTag (lsRemoteOutput) {
|
|
60
|
+
const newer = (a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2]
|
|
61
|
+
let best = null
|
|
62
|
+
for (const line of String(lsRemoteOutput || '').split('\n')) {
|
|
63
|
+
const m = line.match(/refs\/tags\/(v(\d+)\.(\d+)\.(\d+))$/)
|
|
64
|
+
if (!m) continue
|
|
65
|
+
const v = m.slice(2, 5).map(Number)
|
|
66
|
+
if (!best || newer(v, best.v) > 0) best = { tag: m[1], v }
|
|
67
|
+
}
|
|
68
|
+
return best && best.tag
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function latestTagFromGit (url) {
|
|
72
|
+
const { execFileSync } = require('child_process')
|
|
73
|
+
return highestStableTag(execFileSync('git', ['ls-remote', '--tags', '--refs', url], { encoding: 'utf8', timeout: 60000 }))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function toTag (tagName) {
|
|
77
|
+
if (!tagName) return null
|
|
78
|
+
return tagName.startsWith('v') ? tagName : `v${tagName}`
|
|
79
|
+
}
|
|
4
80
|
|
|
5
|
-
|
|
81
|
+
// The upstream connect source asks to be pinned with `tags: latest` as its
|
|
82
|
+
// only ref. A source with any other refs, such as a fork or a PR branch used
|
|
83
|
+
// to preview a connect change, keeps what the playbook says.
|
|
84
|
+
function wantsLatest (source) {
|
|
85
|
+
if (!isRemote(source.url) || !isConnectSource(source.url) || !isUpstreamConnect(source.url)) return false
|
|
86
|
+
const tags = Array.isArray(source.tags) ? source.tags : [source.tags]
|
|
87
|
+
const branches = source.branches == null ? [] : [].concat(source.branches)
|
|
88
|
+
return tags.length === 1 && tags[0] === 'latest' && branches.length === 0
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Rewrites the connect content sources that ask for the latest release to
|
|
92
|
+
// use `tag`. Returns true when a source was updated.
|
|
93
|
+
function pinConnectSource (playbook, tag) {
|
|
94
|
+
const sources = playbook && playbook.content && playbook.content.sources
|
|
95
|
+
if (!Array.isArray(sources) || !tag) return false
|
|
96
|
+
let updated = false
|
|
97
|
+
for (const source of sources) {
|
|
98
|
+
if (!wantsLatest(source)) continue
|
|
99
|
+
source.tags = [tag]
|
|
100
|
+
// Without this, Antora's default branch patterns also pull in main.
|
|
101
|
+
source.branches = []
|
|
102
|
+
updated = true
|
|
103
|
+
}
|
|
104
|
+
return updated
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Removes connect-sourced files that must not be published. Mutates the
|
|
108
|
+
// content aggregate and returns what it dropped.
|
|
109
|
+
function filterConnectContent (contentAggregate) {
|
|
110
|
+
const report = { kept: 0, outsideGenerated: 0, providedElsewhere: 0, otherComponents: 0 }
|
|
111
|
+
const fromConnect = (f) => isConnectOrigin(f.src && f.src.origin)
|
|
112
|
+
for (const bucket of contentAggregate) {
|
|
113
|
+
const files = bucket.files || []
|
|
114
|
+
if (!files.some(fromConnect)) continue
|
|
115
|
+
if (bucket.name !== COMPONENT) {
|
|
116
|
+
// Older connect tags name the component redpanda-connect and ship full
|
|
117
|
+
// pages. None of it belongs on the site.
|
|
118
|
+
report.otherComponents += files.filter(fromConnect).length
|
|
119
|
+
bucket.files = files.filter((f) => !fromConnect(f))
|
|
120
|
+
continue
|
|
121
|
+
}
|
|
122
|
+
const provided = new Set(files.filter((f) => !fromConnect(f)).map((f) => f.path))
|
|
123
|
+
bucket.files = files.filter((f) => {
|
|
124
|
+
if (!fromConnect(f)) return true
|
|
125
|
+
if (!KEPT_PATHS.some((p) => f.path.startsWith(p))) {
|
|
126
|
+
report.outsideGenerated++
|
|
127
|
+
return false
|
|
128
|
+
}
|
|
129
|
+
if (provided.has(f.path)) {
|
|
130
|
+
report.providedElsewhere++
|
|
131
|
+
return false
|
|
132
|
+
}
|
|
133
|
+
report.kept++
|
|
134
|
+
return true
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
// A bucket left with no files (an older connect tag's own component) would
|
|
138
|
+
// still register an empty component.
|
|
139
|
+
for (let i = contentAggregate.length - 1; i >= 0; i--) {
|
|
140
|
+
const b = contentAggregate[i]
|
|
141
|
+
if (!(b.files || []).length && (b.origins || []).every(isConnectOrigin)) contentAggregate.splice(i, 1)
|
|
142
|
+
}
|
|
143
|
+
return report
|
|
144
|
+
}
|
|
6
145
|
|
|
7
|
-
module.exports.register = function (
|
|
146
|
+
module.exports.register = function () {
|
|
8
147
|
raiseListenerLimit(this)
|
|
9
|
-
const logger = this.getLogger('modify-connect-tag-playbook-extension')
|
|
148
|
+
const logger = this.getLogger('modify-connect-tag-playbook-extension')
|
|
10
149
|
|
|
11
150
|
this.on('contextStarted', async ({ playbook }) => {
|
|
151
|
+
const sources = (playbook && playbook.content && playbook.content.sources) || []
|
|
152
|
+
// Clear a ref left over from an earlier build in the same process.
|
|
153
|
+
setResolvedConnectRef(null)
|
|
154
|
+
const source = sources.find(wantsLatest)
|
|
155
|
+
if (!source) return
|
|
156
|
+
let tag = null
|
|
12
157
|
try {
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const latestTag = await GetLatestConnectTag();
|
|
19
|
-
if (latestTag) {
|
|
20
|
-
source.tags[0] = `v${latestTag}`;
|
|
21
|
-
this.updateVariables({ playbook });
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
158
|
+
const { Octokit } = await import('@octokit/rest')
|
|
159
|
+
const { retry } = await import('@octokit/plugin-retry')
|
|
160
|
+
const token = getGitHubApiToken()
|
|
161
|
+
const github = new (Octokit.plugin(retry))({ userAgent: 'Redpanda Docs', auth: token || undefined, retry: { doNotRetry: [403, 404, 429] } })
|
|
162
|
+
tag = toTag(await getLatestConnectTag(github, OWNER, REPO, logger))
|
|
25
163
|
} catch (error) {
|
|
26
|
-
logger.
|
|
164
|
+
logger.warn(`GitHub API lookup of the latest Redpanda Connect release failed: ${error.message}`)
|
|
165
|
+
}
|
|
166
|
+
if (!tag) {
|
|
167
|
+
try {
|
|
168
|
+
tag = latestTagFromGit(source.url)
|
|
169
|
+
if (tag) logger.info(`Resolved the latest Redpanda Connect release from git tags: ${tag}`)
|
|
170
|
+
} catch (error) {
|
|
171
|
+
logger.warn(redact(`git ls-remote of ${source.url} failed: ${error.message}`))
|
|
172
|
+
}
|
|
27
173
|
}
|
|
28
|
-
|
|
174
|
+
if (!tag) {
|
|
175
|
+
// Guessing would let Antora's default branch patterns pull in connect's
|
|
176
|
+
// branches, whose docs are unreleased or missing, so stop the build.
|
|
177
|
+
throw new Error('Could not resolve the latest Redpanda Connect release tag for the connect content source. Set a GitHub token or check network access.')
|
|
178
|
+
}
|
|
179
|
+
pinConnectSource(playbook, tag)
|
|
180
|
+
setResolvedConnectRef(tag)
|
|
181
|
+
this.updateVariables({ playbook })
|
|
182
|
+
logger.info(`Sourcing Redpanda Connect reference content from ${tag}`)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
this.on('contentAggregated', ({ contentAggregate }) => {
|
|
186
|
+
const r = filterConnectContent(contentAggregate)
|
|
187
|
+
if (r.kept + r.outsideGenerated + r.providedElsewhere + r.otherComponents === 0) return
|
|
188
|
+
logger.info(
|
|
189
|
+
`Redpanda Connect content: kept ${r.kept} generated files; skipped ${r.providedElsewhere} already provided by another source, ` +
|
|
190
|
+
`${r.outsideGenerated} outside the generated partials and examples, and ${r.otherComponents} from other components`
|
|
191
|
+
)
|
|
192
|
+
})
|
|
29
193
|
}
|
|
194
|
+
|
|
195
|
+
module.exports._internal = { isRemote, isUpstreamConnect, redact, isConnectSource, isConnectOrigin, toTag, highestStableTag, wantsLatest, pinConnectSource, filterConnectContent }
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Shared Redpanda Connect catalog helpers.
|
|
4
|
+
//
|
|
5
|
+
// The connect repo publishes its reference content (partials and examples)
|
|
6
|
+
// through a content source that modify-connect-tag-playbook pins to the latest
|
|
7
|
+
// release tag. Catalog data (support level, deprecation, Cloud availability)
|
|
8
|
+
// must come from the same ref, or the badges and catalog drift from the
|
|
9
|
+
// reference content as soon as connect's main branch changes a flag before a
|
|
10
|
+
// release. This module carries that ref between the extensions and turns the
|
|
11
|
+
// generated partials/platforms/catalog.json into rows shaped like the
|
|
12
|
+
// translated info.csv rows the macros already consume.
|
|
13
|
+
|
|
14
|
+
// Matches the GitHub URL (with or without .git) and a local clone whose
|
|
15
|
+
// directory is named connect, which is how the source looks in local builds.
|
|
16
|
+
function isConnectSource (url) {
|
|
17
|
+
return typeof url === 'string' && /(^|[/:])(redpanda-data\/)?connect(\.git)?\/?$/.test(url)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isConnectOrigin (origin) {
|
|
21
|
+
return !!origin && (isConnectSource(origin.url) || isConnectSource(origin.worktree))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// The connect ref that modify-connect-tag-playbook resolved for this build.
|
|
25
|
+
// Module state, because both extensions run in the same Antora process and
|
|
26
|
+
// Antora gives extensions no other channel that survives from contextStarted
|
|
27
|
+
// to contentClassified without mutating the playbook.
|
|
28
|
+
let resolvedConnectRef = null
|
|
29
|
+
|
|
30
|
+
function setResolvedConnectRef (ref) {
|
|
31
|
+
resolvedConnectRef = ref || null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getResolvedConnectRef () {
|
|
35
|
+
return resolvedConnectRef
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// The GitHub owner and repo of a connect origin URL, so a playbook that
|
|
39
|
+
// previews a fork reads that fork's info.csv. Null for non-GitHub URLs.
|
|
40
|
+
function githubRepoOf (url) {
|
|
41
|
+
const m = String(url || '').match(/github\.com[/:]([^/]+)\/([^/]+?)(\.git)?\/?$/)
|
|
42
|
+
return m ? { owner: m[1], repo: m[2] } : null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// The ref of the connect content source as Antora aggregated it. Only refs
|
|
46
|
+
// that exist on the remote count: a tag, or a branch fetched from the remote.
|
|
47
|
+
// A branch of a local clone may exist only on disk, so it is not used.
|
|
48
|
+
function connectOriginRef (contentCatalog) {
|
|
49
|
+
if (!contentCatalog || typeof contentCatalog.findBy !== 'function') return null
|
|
50
|
+
for (const file of contentCatalog.findBy({ component: 'connect' })) {
|
|
51
|
+
const origin = file.src && file.src.origin
|
|
52
|
+
if (!isConnectOrigin(origin)) continue
|
|
53
|
+
const repo = githubRepoOf(origin.url)
|
|
54
|
+
if (origin.tag || origin.reftype === 'tag') {
|
|
55
|
+
return { ref: origin.tag || origin.refname, ...(repo || {}) }
|
|
56
|
+
}
|
|
57
|
+
if (!origin.worktree && (origin.branch || origin.refname)) {
|
|
58
|
+
return { ref: origin.branch || origin.refname, ...(repo || {}) }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return null
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const CATALOG_RELATIVE = 'platforms/catalog.json'
|
|
65
|
+
|
|
66
|
+
// The generated catalog.json in the content catalog, preferring the copy
|
|
67
|
+
// from the connect content source over one another source provides.
|
|
68
|
+
function findConnectCatalogFile (contentCatalog) {
|
|
69
|
+
if (!contentCatalog || typeof contentCatalog.findBy !== 'function') return null
|
|
70
|
+
const candidates = contentCatalog
|
|
71
|
+
.findBy({ component: 'connect', module: 'components', family: 'partial' })
|
|
72
|
+
.filter((f) => f.src && f.src.relative === CATALOG_RELATIVE)
|
|
73
|
+
return candidates.find((f) => isConnectOrigin(f.src.origin)) || candidates[0] || null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Page directories and data rows name some types differently: pages live in
|
|
77
|
+
// metrics/ and rate_limits/, while info.csv and the schema say metric and
|
|
78
|
+
// rate_limit. Normalize to the singular data form for lookups.
|
|
79
|
+
function normalizeType (type) {
|
|
80
|
+
const t = String(type || '').trim().toLowerCase().replace(/-/g, '_')
|
|
81
|
+
return t.endsWith('s') ? t.slice(0, -1) : t
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const PAGE_TYPE_DIRS = ['inputs', 'outputs', 'processors', 'caches', 'rate_limits', 'buffers', 'metrics', 'tracers', 'scanners']
|
|
85
|
+
|
|
86
|
+
// The component type of a connector page from its path, for example
|
|
87
|
+
// inputs/kafka.adoc -> input. Null for pages outside the type directories.
|
|
88
|
+
function typeFromRelative (relative) {
|
|
89
|
+
const m = String(relative || '').match(/(?:^|\/)([a-z_]+)\/[^/]+\.adoc$/)
|
|
90
|
+
return m && PAGE_TYPE_DIRS.includes(m[1]) ? normalizeType(m[1]) : null
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const yn = (value) => (value === true || String(value || '').trim().toLowerCase() === 'y' ? 'y' : 'n')
|
|
94
|
+
|
|
95
|
+
// Converts catalog.json entries into rows with the raw info.csv column names,
|
|
96
|
+
// so they go through the same translation as rows parsed from info.csv. The
|
|
97
|
+
// catalog adds status, categories, cgo_only, and the full commercial name list.
|
|
98
|
+
function catalogEntriesToCsvRows (entries) {
|
|
99
|
+
if (!Array.isArray(entries)) throw new Error('catalog.json is not a JSON array')
|
|
100
|
+
return entries
|
|
101
|
+
.filter((e) => e && e.name && e.type)
|
|
102
|
+
.map((e) => {
|
|
103
|
+
const commercialNames = Array.isArray(e.commercial_names) ? e.commercial_names.filter(Boolean) : []
|
|
104
|
+
const status = String(e.status || '').trim().toLowerCase()
|
|
105
|
+
return {
|
|
106
|
+
name: e.name,
|
|
107
|
+
type: e.type,
|
|
108
|
+
commercial_name: commercialNames[0] || e.name,
|
|
109
|
+
commercial_names: commercialNames,
|
|
110
|
+
support: e.support || '',
|
|
111
|
+
deprecated: status === 'deprecated' ? 'y' : 'n',
|
|
112
|
+
cloud: yn(e.cloud),
|
|
113
|
+
cloud_with_gpu: yn(e.cloud_ai),
|
|
114
|
+
status,
|
|
115
|
+
version: e.version || '',
|
|
116
|
+
categories: Array.isArray(e.categories) ? e.categories : [],
|
|
117
|
+
cgo_only: yn(e.cgo_only),
|
|
118
|
+
summary: e.summary || ''
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Cloud availability of a translated row. Cloud has two pipeline flavors:
|
|
124
|
+
// standard (info.csv `cloud`) and GPU (info.csv `cloud_with_gpu`, catalog
|
|
125
|
+
// `cloud_ai`). A component in either one has a Cloud page.
|
|
126
|
+
function isCloudAvailable (row) {
|
|
127
|
+
return !!row && (row.is_cloud_supported === 'y' || row.cloud_ai === 'y')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// gpu_only: only in GPU pipelines (for example the ollama processors).
|
|
131
|
+
// no_gpu: in standard pipelines but not in GPU pipelines (for example jira).
|
|
132
|
+
function gpuFlags (cloud, cloudAi) {
|
|
133
|
+
return {
|
|
134
|
+
gpu_only: cloud !== 'y' && cloudAi === 'y' ? 'y' : 'n',
|
|
135
|
+
no_gpu: cloud === 'y' && cloudAi !== 'y' ? 'y' : 'n'
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = {
|
|
140
|
+
CATALOG_RELATIVE,
|
|
141
|
+
isConnectSource,
|
|
142
|
+
isConnectOrigin,
|
|
143
|
+
setResolvedConnectRef,
|
|
144
|
+
getResolvedConnectRef,
|
|
145
|
+
githubRepoOf,
|
|
146
|
+
connectOriginRef,
|
|
147
|
+
findConnectCatalogFile,
|
|
148
|
+
normalizeType,
|
|
149
|
+
typeFromRelative,
|
|
150
|
+
catalogEntriesToCsvRows,
|
|
151
|
+
isCloudAvailable,
|
|
152
|
+
gpuFlags
|
|
153
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const { posix: path } = require('path')
|
|
3
3
|
const logger = require('@antora/logger')('rp-connect-components-macro')
|
|
4
|
+
const { isCloudAvailable, typeFromRelative } = require('../extensions/util/connect-catalog')
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Registers macros for use in Redpanda Connect contexts in the Redpanda documentation.
|
|
@@ -220,8 +221,9 @@ module.exports.register = function (registry, context) {
|
|
|
220
221
|
*/
|
|
221
222
|
function processConnectors(parsedData) {
|
|
222
223
|
return parsedData.data.reduce((connectors, row) => {
|
|
223
|
-
const { connector, commercial_name, type, support_level,
|
|
224
|
-
|
|
224
|
+
const { connector, commercial_name, type, support_level, is_licensed, redpandaConnectUrl, redpandaCloudUrl } = row;
|
|
225
|
+
// Available in standard or GPU Cloud pipelines
|
|
226
|
+
const isCloudSupported = isCloudAvailable(row);
|
|
225
227
|
|
|
226
228
|
// Initialize the connector if it's not already in the map
|
|
227
229
|
if (!connectors[connector]) {
|
|
@@ -245,7 +247,9 @@ module.exports.register = function (registry, context) {
|
|
|
245
247
|
redpandaCloudUrl: redpandaCloudUrl || ''
|
|
246
248
|
},
|
|
247
249
|
supportLevel: support_level,
|
|
248
|
-
isCloudSupported: isCloudSupported
|
|
250
|
+
isCloudSupported: isCloudSupported,
|
|
251
|
+
gpuOnly: row.gpu_only === 'y',
|
|
252
|
+
noGpu: row.no_gpu === 'y'
|
|
249
253
|
};
|
|
250
254
|
}
|
|
251
255
|
|
|
@@ -301,8 +305,8 @@ module.exports.register = function (registry, context) {
|
|
|
301
305
|
};
|
|
302
306
|
|
|
303
307
|
parsedData.data.forEach(row => {
|
|
304
|
-
const { connector: driverName, commercial_name, support_level
|
|
305
|
-
const isCloudSupported =
|
|
308
|
+
const { connector: driverName, commercial_name, support_level } = row;
|
|
309
|
+
const isCloudSupported = isCloudAvailable(row);
|
|
306
310
|
const supportLevel = support_level.toLowerCase();
|
|
307
311
|
|
|
308
312
|
// Only process SQL drivers
|
|
@@ -463,10 +467,20 @@ module.exports.register = function (registry, context) {
|
|
|
463
467
|
const firstCloudSupportedType = Array.from(types.entries())
|
|
464
468
|
.map(([_, commercialNames]) => Object.values(commercialNames).find(({ isCloudSupported }) => isCloudSupported))
|
|
465
469
|
.find(entry => entry);
|
|
470
|
+
// Say which Cloud pipelines when every Cloud-available type agrees:
|
|
471
|
+
// the ollama processors run only in GPU pipelines, and jira only outside them.
|
|
472
|
+
const cloudEntries = Array.from(types.values())
|
|
473
|
+
.flatMap(commercialNames => Object.values(commercialNames))
|
|
474
|
+
.filter(({ isCloudSupported }) => isCloudSupported);
|
|
475
|
+
const pipelineNote = cloudEntries.length && cloudEntries.every(e => e.gpuOnly)
|
|
476
|
+
? ' (GPU pipelines only)'
|
|
477
|
+
: cloudEntries.length && cloudEntries.every(e => e.noGpu)
|
|
478
|
+
? ' (not in GPU pipelines)'
|
|
479
|
+
: '';
|
|
466
480
|
const cloudLinkDisplay = firstCloudSupportedType
|
|
467
481
|
? firstCloudSupportedType.urls.redpandaCloudUrl
|
|
468
|
-
? `<a href="${firstCloudSupportedType.urls.redpandaCloudUrl}">Yes</a
|
|
469
|
-
: `Yes`
|
|
482
|
+
? `<a href="${firstCloudSupportedType.urls.redpandaCloudUrl}">Yes</a>${pipelineNote}`
|
|
483
|
+
: `Yes${pipelineNote}`
|
|
470
484
|
: 'No';
|
|
471
485
|
|
|
472
486
|
const firstUrl = getFirstUrlFromTypesArray(Array.from(types.entries()), isCloud);
|
|
@@ -1018,7 +1032,8 @@ module.exports.register = function (registry, context) {
|
|
|
1018
1032
|
const attributes = parent.getDocument().getAttributes();
|
|
1019
1033
|
const component = attributes['page-component-title']; // Current component (for example, 'Redpanda Cloud' or 'Redpanda Connect')
|
|
1020
1034
|
const name = attributes['doctitle'];
|
|
1021
|
-
|
|
1035
|
+
// Drafted pages no longer freeze :type:, so fall back to the page's directory.
|
|
1036
|
+
const type = attributes['type'] || typeFromRelative(attributes['page-relative-src-path']);
|
|
1022
1037
|
if (!name || !type) {
|
|
1023
1038
|
return self.createBlock(parent, 'pass', '');
|
|
1024
1039
|
}
|
package/package.json
CHANGED
|
@@ -33,7 +33,10 @@ const SURFACE_ROUTES = [
|
|
|
33
33
|
// console) and charts/<name>/values.yaml (connectors).
|
|
34
34
|
{ surface: 'helm', pattern: /^charts\/[^/]+\/(chart\/)?values\.yaml$/ },
|
|
35
35
|
{ surface: 'crd', pattern: /^operator\/api\// },
|
|
36
|
-
|
|
36
|
+
// Redpanda Connect publishes strings from component packages, the helper
|
|
37
|
+
// packages they share (internal/httpclient, internal/retries, ...), the
|
|
38
|
+
// public schema and the `rpk connect` CLI, plus config templates.
|
|
39
|
+
{ surface: 'connect', pattern: /^(?:internal|public|cmd)\/(?!(?:.*\/)?testdata\/)(?:.*(?<!_test)\.go|.*\.tmpl\.ya?ml)$/ },
|
|
37
40
|
// API protos, whose comments and openapiv2 option strings reach readers as
|
|
38
41
|
// OpenAPI descriptions. console holds the data plane and Console APIs under
|
|
39
42
|
// proto/redpanda/api/; cloudv2's control plane lives under proto/public/.
|
|
@@ -36,6 +36,7 @@ const SEVERITIES = ['error', 'warning', 'info']
|
|
|
36
36
|
* - runOnUnverifiable: also run when decl.meta.unverifiable is set
|
|
37
37
|
* (all other rules are skipped for unverifiable declarations, so a
|
|
38
38
|
* non-literal source string can never produce an error)
|
|
39
|
+
* A declaration skips any rule named in decl.meta.skip_rules.
|
|
39
40
|
* @param {Object} options - { skipRules: [names], onlyRules: [names] }
|
|
40
41
|
* @returns {Object} { findings, summary: { byRule, bySurface } }
|
|
41
42
|
*/
|
|
@@ -55,6 +56,10 @@ function runRules (declarations, rules, options = {}) {
|
|
|
55
56
|
// Unverifiable declarations (for example, a metric description built
|
|
56
57
|
// with fmt::format) only run rules that opt in; they must never error.
|
|
57
58
|
if (decl.meta && decl.meta.unverifiable && !rule.runOnUnverifiable) continue
|
|
59
|
+
// A surface can opt one declaration out of a rule whose premise does
|
|
60
|
+
// not hold for it (connect's CLI help is not AsciiDoc, though the
|
|
61
|
+
// rest of the surface is).
|
|
62
|
+
if (decl.meta && Array.isArray(decl.meta.skip_rules) && decl.meta.skip_rules.includes(rule.name)) continue
|
|
58
63
|
|
|
59
64
|
try {
|
|
60
65
|
for (const issue of rule.check(decl) || []) {
|
|
@@ -53,7 +53,7 @@ func fixtureConfigFields() []*service.ConfigField {
|
|
|
53
53
|
Description("Poll interval."),
|
|
54
54
|
// Deprecated fields are exempt from the missing-description rule.
|
|
55
55
|
service.NewStringField("old_token").Deprecated().Default(""),
|
|
56
|
-
// Dynamic description:
|
|
56
|
+
// Dynamic description: reported with a placeholder, never guessed at.
|
|
57
57
|
service.NewStringField("dynamic_field").
|
|
58
58
|
Description(dynamicDescription()),
|
|
59
59
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
package cli
|
|
2
|
+
|
|
3
|
+
import "github.com/urfave/cli/v2"
|
|
4
|
+
|
|
5
|
+
var lintCommand = &cli.Command{
|
|
6
|
+
Name: "lint",
|
|
7
|
+
Usage: "Parse configs and report any linting errors",
|
|
8
|
+
Description: "Exits with a status code 1 if any linting errors are detected.",
|
|
9
|
+
Flags: []cli.Flag{
|
|
10
|
+
&cli.BoolFlag{
|
|
11
|
+
Name: "deprecated",
|
|
12
|
+
Usage: "Print linting errors for the presence of deprecated fields.",
|
|
13
|
+
},
|
|
14
|
+
&cli.StringFlag{
|
|
15
|
+
Name: "format",
|
|
16
|
+
Usage: "Output format",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Package azure is a lint-strings fixture mirroring connect's Azure package:
|
|
2
|
+
// specs start from a helper constructor and share parameterized fields.
|
|
3
|
+
package azure
|
|
4
|
+
|
|
5
|
+
import "github.com/redpanda-data/benthos/v4/public/service"
|
|
6
|
+
|
|
7
|
+
const azureFieldAccount = "storage_account"
|
|
8
|
+
|
|
9
|
+
// azureComponentSpec builds the spec every Azure component starts from.
|
|
10
|
+
func azureComponentSpec() *service.ConfigSpec {
|
|
11
|
+
return service.NewConfigSpec().
|
|
12
|
+
Categories("Services", "Azure")
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// accountField is called once per component, with a different product.
|
|
16
|
+
func accountField(product string) *service.ConfigField {
|
|
17
|
+
return service.NewStringField(azureFieldAccount).
|
|
18
|
+
Description("The storage account to access for " + product + ".").
|
|
19
|
+
ShortDescription("The `storage account` to access.")
|
|
20
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
package azure
|
|
2
|
+
|
|
3
|
+
// A constant and a helper in another file of the package.
|
|
4
|
+
const thingProduct = "Azure Thing Storage"
|
|
5
|
+
|
|
6
|
+
func thingDescription() string {
|
|
7
|
+
return "Reads every thing in a container. See xref:components:outputs/azure_thing.adoc[the output] and xref:outputs/azure_thing.adoc[the output again]."
|
|
8
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
package azure
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"github.com/redpanda-data/benthos/v4/public/service"
|
|
5
|
+
|
|
6
|
+
"github.com/redpanda-data/connect/v4/internal/retries"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
func thingInputSpec() *service.ConfigSpec {
|
|
10
|
+
spec := azureComponentSpec().
|
|
11
|
+
Summary("Reads things from " + thingProduct + ".").
|
|
12
|
+
Description(thingDescription()).
|
|
13
|
+
Footnotes("== Throughput\n\nThe input reads one page of things per request.").
|
|
14
|
+
Example("Read every thing", "Reads each thing once and stops.", `input: {}`).
|
|
15
|
+
|
|
16
|
+
// A blank line and a comment inside the chain do not end it.
|
|
17
|
+
Field(accountField("Azure Thing input")).
|
|
18
|
+
Fields(retries.CommonRetryBackOffFields(3, "1s", "5s", "30s")...)
|
|
19
|
+
spec = spec.Field(service.NewStringAnnotatedEnumField("mode", map[string]string{
|
|
20
|
+
"once": "Reads each thing once.",
|
|
21
|
+
"follow": "Keeps reading new things as they arrive.",
|
|
22
|
+
}))
|
|
23
|
+
// Documented by reassignment: not a missing description.
|
|
24
|
+
f := service.NewStringField("prefix")
|
|
25
|
+
f = f.Description("Only things whose name starts with this prefix are read.")
|
|
26
|
+
spec = spec.Field(f)
|
|
27
|
+
return spec
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func init() {
|
|
31
|
+
service.MustRegisterInput("azure_thing", thingInputSpec(), nil)
|
|
32
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
package azure
|
|
2
|
+
|
|
3
|
+
import "github.com/redpanda-data/benthos/v4/public/service"
|
|
4
|
+
|
|
5
|
+
func init() {
|
|
6
|
+
conf := azureComponentSpec().
|
|
7
|
+
Summary("Writes things to " + thingProduct + ".")
|
|
8
|
+
conf = conf.Field(accountField("Azure Thing output"))
|
|
9
|
+
// A field added in place, without reassignment.
|
|
10
|
+
conf.Field(service.NewStringField("container").Description("The container to write things to."))
|
|
11
|
+
service.MustRegisterOutput("azure_thing", conf, nil)
|
|
12
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
package widget
|
|
2
|
+
|
|
3
|
+
import "github.com/redpanda-data/benthos/v4/public/bloblang"
|
|
4
|
+
|
|
5
|
+
func init() {
|
|
6
|
+
spec := bloblang.NewPluginSpec().
|
|
7
|
+
Category("Encoding").
|
|
8
|
+
Description("Encodes a widget as a string.").
|
|
9
|
+
Param(bloblang.NewStringParam("format").Description("The format to encode the widget in.")).
|
|
10
|
+
Example("Encode a widget as JSON.", `root = this.encode_widget("json")`, [2]string{`{}`, `"{}"`})
|
|
11
|
+
bloblang.MustRegisterMethodV2("encode_widget", spec, nil)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type note struct{}
|
|
15
|
+
|
|
16
|
+
// Description on a type that is not a benthos spec.
|
|
17
|
+
func (n note) Description(s string) note { return n }
|
|
18
|
+
|
|
19
|
+
func init() {
|
|
20
|
+
_ = note{}.Description("Not a published string.")
|
|
21
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
package widget
|
|
2
|
+
|
|
3
|
+
import "github.com/redpanda-data/benthos/v4/public/service"
|
|
4
|
+
|
|
5
|
+
func linesScannerSpec() *service.ConfigSpec {
|
|
6
|
+
return service.NewConfigSpec().
|
|
7
|
+
Summary("Splits a widget stream into lines.")
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
func init() {
|
|
11
|
+
service.MustRegisterBatchScannerCreator("widget_lines", linesScannerSpec(), nil)
|
|
12
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Package retries is a helper package outside internal/impl whose fields
|
|
2
|
+
// appear on every component that calls it.
|
|
3
|
+
package retries
|
|
4
|
+
|
|
5
|
+
import "github.com/redpanda-data/benthos/v4/public/service"
|
|
6
|
+
|
|
7
|
+
// CommonRetryBackOffFields returns the common retry fields.
|
|
8
|
+
func CommonRetryBackOffFields(defaultMaxRetries int, initial, maxInterval, maxElapsed string) []*service.ConfigField {
|
|
9
|
+
return []*service.ConfigField{
|
|
10
|
+
service.NewIntField("max_retries").
|
|
11
|
+
Description("The maximum number of retries before giving up on the request."),
|
|
12
|
+
}
|
|
13
|
+
}
|