@nitra/cursor 1.8.111 → 1.8.112
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/mdc/abie.mdc +1 -1
- package/package.json +1 -1
- package/scripts/check-abie.mjs +217 -0
package/mdc/abie.mdc
CHANGED
|
@@ -116,7 +116,7 @@ spec:
|
|
|
116
116
|
## k8s: overlay **ru** і nginx-sidecar для WebSocket (Hasura)
|
|
117
117
|
|
|
118
118
|
YC ALB (gwin) має баг: якщо HTTPRoute-правило містить одночасно `URLRewrite` (ReplacePrefixMatch) і `upgrade_types: websocket` — ALB не обробляє WebSocket і повертає 404.
|
|
119
|
-
https://center.yandex.cloud/support/tickets/TX549394
|
|
119
|
+
<https://center.yandex.cloud/support/tickets/TX549394>
|
|
120
120
|
|
|
121
121
|
Обхідний варіант: nginx-sidecar у поді, що сам виконує rewrite і проксіює WebSocket до Hasura.
|
|
122
122
|
|
package/package.json
CHANGED
package/scripts/check-abie.mjs
CHANGED
|
@@ -89,6 +89,23 @@ const REMOVE_CLUSTER_IP_BEFORE_OP_RE = /path:\s*\/spec\/clusterIP\b[\s\S]{0,200}
|
|
|
89
89
|
const REMOVE_CLUSTER_IPS_AFTER_OP_RE = /op:\s*remove\b[\s\S]{0,200}?path:\s*\/spec\/clusterIPs\b/mu
|
|
90
90
|
const REMOVE_CLUSTER_IPS_BEFORE_OP_RE = /path:\s*\/spec\/clusterIPs\b[\s\S]{0,200}?op:\s*remove\b/mu
|
|
91
91
|
|
|
92
|
+
/** Підрядок образу Hasura у контейнері Deployment (abie.mdc nginx-sidecar). */
|
|
93
|
+
const HASURA_IMAGE_MARKER = 'hasura/graphql-engine'
|
|
94
|
+
/** Nginx-sidecar image (abie.mdc): nginx:*-alpine. */
|
|
95
|
+
const NGINX_SIDECAR_IMAGE_RE = /image:\s*nginx:\S*-alpine/u
|
|
96
|
+
/** containerPort: 8081 у patch Deployment (abie.mdc). */
|
|
97
|
+
const NGINX_SIDECAR_CONTAINER_PORT_RE = /containerPort:\s*8081\b/u
|
|
98
|
+
/** port: 8081 у patch Service -hl (proxy порт, abie.mdc). */
|
|
99
|
+
const PATCH_PROXY_PORT_8081_RE = /\bport:\s*8081\b/u
|
|
100
|
+
/** configmap-nginx.yaml у resources kustomization (abie.mdc). */
|
|
101
|
+
const RESOURCES_CONFIGMAP_NGINX_RE = /configmap-nginx\.yaml/u
|
|
102
|
+
/** path /spec/rules/{i}/backendRefs/{j}/port … value: 8081 у patch HTTPRoute (path→value, abie.mdc). */
|
|
103
|
+
const HTTPROUTE_BACKENDREF_PORT_8081_RE =
|
|
104
|
+
/path:\s*\/spec\/rules\/\d+\/backendRefs\/\d+\/port\b[\s\S]{0,200}?value:\s*8081\b/mu
|
|
105
|
+
/** Те саме, value→path. */
|
|
106
|
+
const HTTPROUTE_BACKENDREF_PORT_8081_VALUE_FIRST_RE =
|
|
107
|
+
/value:\s*8081\b[\s\S]{0,200}?path:\s*\/spec\/rules\/\d+\/backendRefs\/\d+\/port\b/mu
|
|
108
|
+
|
|
92
109
|
/** Гілки, які мають бути в **`ignore_branches`** за abie.mdc. */
|
|
93
110
|
export const ABIE_REQUIRED_IGNORE_BRANCHES = ['dev', 'ua', 'ru']
|
|
94
111
|
|
|
@@ -1627,6 +1644,203 @@ async function checkHcYamlFiles(root, deploymentDirs, pass, fail) {
|
|
|
1627
1644
|
}
|
|
1628
1645
|
}
|
|
1629
1646
|
|
|
1647
|
+
/**
|
|
1648
|
+
* Чи Deployment-документ містить контейнер із образом **`hasura/graphql-engine`** (abie.mdc nginx-sidecar).
|
|
1649
|
+
* @param {unknown} obj корінь YAML-документа
|
|
1650
|
+
* @returns {boolean}
|
|
1651
|
+
*/
|
|
1652
|
+
function deploymentDocHasHasuraImage(obj) {
|
|
1653
|
+
if (!isDeploymentDoc(obj)) return false
|
|
1654
|
+
const rec = /** @type {Record<string, unknown>} */ (obj)
|
|
1655
|
+
const spec = rec.spec
|
|
1656
|
+
if (spec === null || typeof spec !== 'object' || Array.isArray(spec)) return false
|
|
1657
|
+
const template = /** @type {Record<string, unknown>} */ (spec).template
|
|
1658
|
+
if (template === null || typeof template !== 'object' || Array.isArray(template)) return false
|
|
1659
|
+
const podSpec = /** @type {Record<string, unknown>} */ (template).spec
|
|
1660
|
+
if (podSpec === null || typeof podSpec !== 'object' || Array.isArray(podSpec)) return false
|
|
1661
|
+
const containers = /** @type {Record<string, unknown>} */ (podSpec).containers
|
|
1662
|
+
if (!Array.isArray(containers)) return false
|
|
1663
|
+
for (const c of containers) {
|
|
1664
|
+
if (c !== null && typeof c === 'object' && !Array.isArray(c)) {
|
|
1665
|
+
const img = /** @type {Record<string, unknown>} */ (c).image
|
|
1666
|
+
if (typeof img === 'string' && img.includes(HASURA_IMAGE_MARKER)) return true
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
return false
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
/**
|
|
1673
|
+
* Чи Kustomization-документ містить у **`images[*].newName`** рядок **`hasura/graphql-engine`** (abie.mdc nginx-sidecar).
|
|
1674
|
+
* @param {unknown} obj корінь YAML-документа
|
|
1675
|
+
* @returns {boolean}
|
|
1676
|
+
*/
|
|
1677
|
+
function kustomizationDocHasHasuraImageNewName(obj) {
|
|
1678
|
+
if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) return false
|
|
1679
|
+
const rec = /** @type {Record<string, unknown>} */ (obj)
|
|
1680
|
+
if (!Array.isArray(rec.images)) return false
|
|
1681
|
+
for (const img of rec.images) {
|
|
1682
|
+
if (img !== null && typeof img === 'object' && !Array.isArray(img)) {
|
|
1683
|
+
const newName = /** @type {Record<string, unknown>} */ (img).newName
|
|
1684
|
+
if (typeof newName === 'string' && newName.includes(HASURA_IMAGE_MARKER)) return true
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
return false
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
/**
|
|
1691
|
+
* Збирає тексти inline **patch** для **Deployment** з **kustomization.yaml** (усі документи).
|
|
1692
|
+
* @param {string} raw повний текст файлу
|
|
1693
|
+
* @returns {string[]} рядки patch
|
|
1694
|
+
*/
|
|
1695
|
+
function collectDeploymentPatchTextsFromKustomization(raw) {
|
|
1696
|
+
const body = stripBom(raw)
|
|
1697
|
+
const lines = body.split(LINE_SPLIT_RE)
|
|
1698
|
+
const first = lines[0] ?? ''
|
|
1699
|
+
const rest = MODELINE_RE.test(first.trim()) ? lines.slice(1).join('\n') : body
|
|
1700
|
+
/** @type {import('yaml').Document[]} */
|
|
1701
|
+
let docs
|
|
1702
|
+
try {
|
|
1703
|
+
docs = parseAllDocuments(rest)
|
|
1704
|
+
} catch {
|
|
1705
|
+
return []
|
|
1706
|
+
}
|
|
1707
|
+
/** @type {string[]} */
|
|
1708
|
+
const out = []
|
|
1709
|
+
for (const doc of docs) {
|
|
1710
|
+
if (doc.errors.length > 0) continue
|
|
1711
|
+
const root = doc.toJSON()
|
|
1712
|
+
if (root === null || typeof root !== 'object' || Array.isArray(root)) continue
|
|
1713
|
+
const rec = /** @type {Record<string, unknown>} */ (root)
|
|
1714
|
+
if (!Array.isArray(rec.patches)) continue
|
|
1715
|
+
for (const p of rec.patches) {
|
|
1716
|
+
if (p === null || typeof p !== 'object' || Array.isArray(p)) continue
|
|
1717
|
+
const pr = /** @type {Record<string, unknown>} */ (p)
|
|
1718
|
+
const target = pr.target
|
|
1719
|
+
if (target === null || typeof target !== 'object' || Array.isArray(target)) continue
|
|
1720
|
+
const tg = /** @type {Record<string, unknown>} */ (target)
|
|
1721
|
+
if (tg.kind !== 'Deployment') continue
|
|
1722
|
+
const patchStr = pr.patch
|
|
1723
|
+
if (typeof patchStr === 'string' && patchStr.trim() !== '') out.push(patchStr)
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
return out
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
/**
|
|
1730
|
+
* Каталоги пакетів, де в дереві **k8s** є **Deployment** з образом **`hasura/graphql-engine`** або
|
|
1731
|
+
* **Kustomization** з **`images[*].newName`** на нього (abie.mdc nginx-sidecar).
|
|
1732
|
+
* @param {string} root корінь репозиторію
|
|
1733
|
+
* @param {string[]} yamlAbs абсолютні шляхи yaml під k8s
|
|
1734
|
+
* @returns {Promise<Set<string>>} абсолютні шляхи каталогів пакетів
|
|
1735
|
+
*/
|
|
1736
|
+
async function collectHasuraK8sPackageDirs(root, yamlAbs) {
|
|
1737
|
+
/** @type {Set<string>} */
|
|
1738
|
+
const dirs = new Set()
|
|
1739
|
+
for (const abs of yamlAbs) {
|
|
1740
|
+
const rel = relative(root, abs).replaceAll('\\', '/') || abs
|
|
1741
|
+
const docs = await readAndParseYamlDocs(abs, rel, silentFail)
|
|
1742
|
+
if (!docs) continue
|
|
1743
|
+
for (const doc of docs) {
|
|
1744
|
+
if (doc.errors.length > 0) continue
|
|
1745
|
+
const obj = doc.toJSON()
|
|
1746
|
+
if (deploymentDocHasHasuraImage(obj) || kustomizationDocHasHasuraImageNewName(obj)) {
|
|
1747
|
+
const pkgDir = abiePackageDirFromK8sYamlRel(root, rel)
|
|
1748
|
+
if (pkgDir) dirs.add(pkgDir)
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
return dirs
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
/**
|
|
1756
|
+
* Якщо в дереві **k8s** є Deployment з **`hasura/graphql-engine`** і **`ru/kustomization.yaml`** містить
|
|
1757
|
+
* **`HASURA_GRAPHQL_JWT_SECRET`** — вимагає **nginx-sidecar** (abie.mdc):
|
|
1758
|
+
* **`ru/configmap-nginx.yaml`**, **`resources`** у kustomization, patch **Service -hl** (port 8081),
|
|
1759
|
+
* patch **Deployment** (nginx-sidecar image + containerPort 8081), patch **HTTPRoute** (port 8081).
|
|
1760
|
+
* @param {string} root корінь репозиторію
|
|
1761
|
+
* @param {string[]} yamlFilesAbs yaml під k8s
|
|
1762
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
1763
|
+
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
1764
|
+
* @returns {Promise<void>}
|
|
1765
|
+
*/
|
|
1766
|
+
async function ensureAbieNginxSidecarForHasura(root, yamlFilesAbs, fail, passFn) {
|
|
1767
|
+
const hasuraPkgDirs = await collectHasuraK8sPackageDirs(root, yamlFilesAbs)
|
|
1768
|
+
if (hasuraPkgDirs.size === 0) {
|
|
1769
|
+
passFn('Немає Deployment із hasura/graphql-engine у дереві k8s — nginx-sidecar не вимагається (abie.mdc)')
|
|
1770
|
+
return
|
|
1771
|
+
}
|
|
1772
|
+
for (const pkgAbs of [...hasuraPkgDirs].toSorted((a, b) => a.localeCompare(b))) {
|
|
1773
|
+
const relPkg = relative(root, pkgAbs).replaceAll('\\', '/') || pkgAbs
|
|
1774
|
+
const ruAbs = join(pkgAbs, 'k8s', 'ru', 'kustomization.yaml')
|
|
1775
|
+
if (!existsSync(ruAbs)) {
|
|
1776
|
+
passFn(`${relPkg}/k8s: є Hasura Deployment, але немає ru/kustomization.yaml — nginx-sidecar не перевіряється`)
|
|
1777
|
+
continue
|
|
1778
|
+
}
|
|
1779
|
+
let ruRaw
|
|
1780
|
+
try {
|
|
1781
|
+
ruRaw = await readFile(ruAbs, 'utf8')
|
|
1782
|
+
} catch (error) {
|
|
1783
|
+
const msg = error instanceof Error ? error.message : String(error)
|
|
1784
|
+
fail(`${relPkg}/k8s/ru/kustomization.yaml: не вдалося прочитати (${msg})`)
|
|
1785
|
+
return
|
|
1786
|
+
}
|
|
1787
|
+
if (!ruRaw.includes(HASURA_JWT_SECRET_IN_KUSTOMIZATION)) {
|
|
1788
|
+
passFn(
|
|
1789
|
+
`${relPkg}/k8s/ru/kustomization.yaml: немає ${HASURA_JWT_SECRET_IN_KUSTOMIZATION} — nginx-sidecar не вимагається (abie.mdc)`
|
|
1790
|
+
)
|
|
1791
|
+
continue
|
|
1792
|
+
}
|
|
1793
|
+
const relRu = relative(root, ruAbs).replaceAll('\\', '/') || ruAbs
|
|
1794
|
+
// configmap-nginx.yaml must exist
|
|
1795
|
+
const configmapNginxAbs = join(pkgAbs, 'k8s', 'ru', 'configmap-nginx.yaml')
|
|
1796
|
+
if (!existsSync(configmapNginxAbs)) {
|
|
1797
|
+
fail(`${relPkg}/k8s/ru: потрібен configmap-nginx.yaml з nginx.conf (nginx-sidecar для Hasura WebSocket, abie.mdc)`)
|
|
1798
|
+
return
|
|
1799
|
+
}
|
|
1800
|
+
passFn(`${relPkg}/k8s/ru/configmap-nginx.yaml: існує`)
|
|
1801
|
+
// kustomization resources must include configmap-nginx.yaml
|
|
1802
|
+
if (!RESOURCES_CONFIGMAP_NGINX_RE.test(ruRaw)) {
|
|
1803
|
+
fail(`${relRu}: у resources потрібен configmap-nginx.yaml (nginx-sidecar, abie.mdc)`)
|
|
1804
|
+
return
|
|
1805
|
+
}
|
|
1806
|
+
passFn(`${relRu}: resources містить configmap-nginx.yaml`)
|
|
1807
|
+
// Service -hl patch must include port: 8081 (proxy)
|
|
1808
|
+
const svcPatchByName = collectAbieRuServicePatchTextByTargetNameFromRaw(ruRaw)
|
|
1809
|
+
const hasHlWith8081 = [...svcPatchByName.entries()].some(
|
|
1810
|
+
([name, pt]) => name.endsWith('-hl') && PATCH_PROXY_PORT_8081_RE.test(pt)
|
|
1811
|
+
)
|
|
1812
|
+
if (!hasHlWith8081) {
|
|
1813
|
+
fail(`${relRu}: у patch Service -hl потрібен port: 8081 (proxy) для nginx-sidecar (abie.mdc)`)
|
|
1814
|
+
return
|
|
1815
|
+
}
|
|
1816
|
+
passFn(`${relRu}: Service -hl patch містить port 8081 (nginx-sidecar)`)
|
|
1817
|
+
// Deployment patch must include nginx-sidecar (image nginx:*-alpine + containerPort: 8081)
|
|
1818
|
+
const deployPatches = collectDeploymentPatchTextsFromKustomization(ruRaw)
|
|
1819
|
+
const hasNginxSidecar = deployPatches.some(
|
|
1820
|
+
pt => NGINX_SIDECAR_IMAGE_RE.test(pt) && NGINX_SIDECAR_CONTAINER_PORT_RE.test(pt)
|
|
1821
|
+
)
|
|
1822
|
+
if (!hasNginxSidecar) {
|
|
1823
|
+
fail(
|
|
1824
|
+
`${relRu}: у patch Deployment потрібен nginx-sidecar (image nginx:…-alpine, containerPort: 8081) — abie.mdc`
|
|
1825
|
+
)
|
|
1826
|
+
return
|
|
1827
|
+
}
|
|
1828
|
+
passFn(`${relRu}: Deployment patch містить nginx-sidecar (image + containerPort 8081)`)
|
|
1829
|
+
// HTTPRoute patch must replace a backendRef port to 8081
|
|
1830
|
+
const combined = getCombinedNginxRunPatchTextFromKustomization(ruRaw)
|
|
1831
|
+
if (
|
|
1832
|
+
!HTTPROUTE_BACKENDREF_PORT_8081_RE.test(combined) &&
|
|
1833
|
+
!HTTPROUTE_BACKENDREF_PORT_8081_VALUE_FIRST_RE.test(combined)
|
|
1834
|
+
) {
|
|
1835
|
+
fail(
|
|
1836
|
+
`${relRu}: у patch HTTPRoute потрібен JSON6902 з path /spec/rules/…/backendRefs/…/port та value: 8081 (nginx-sidecar, abie.mdc)`
|
|
1837
|
+
)
|
|
1838
|
+
return
|
|
1839
|
+
}
|
|
1840
|
+
passFn(`${relRu}: HTTPRoute patch замінює порт на 8081 (nginx-sidecar)`)
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1630
1844
|
/**
|
|
1631
1845
|
* Перевіряє відповідність проєкту правилам abie.mdc.
|
|
1632
1846
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
@@ -1671,5 +1885,8 @@ export async function check() {
|
|
|
1671
1885
|
await ensureUaRuAbieHttpRoutePatches(root, yamlFiles, fail, pass)
|
|
1672
1886
|
}
|
|
1673
1887
|
|
|
1888
|
+
pass('Перевіряємо nginx-sidecar для Hasura WebSocket у ru (abie.mdc)')
|
|
1889
|
+
await ensureAbieNginxSidecarForHasura(root, yamlFiles, fail, pass)
|
|
1890
|
+
|
|
1674
1891
|
return reporter.getExitCode()
|
|
1675
1892
|
}
|