@nitra/cursor 1.8.85 → 1.8.88
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 +38 -4
- package/mdc/k8s.mdc +5 -36
- package/package.json +1 -1
- package/scripts/check-abie.mjs +199 -6
- package/scripts/check-k8s.mjs +4 -0
package/mdc/abie.mdc
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Правила для проєктів AbInBev Efes
|
|
3
3
|
alwaysApply: true
|
|
4
|
-
version: '1.
|
|
4
|
+
version: '1.11'
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Правило **abie** для споживачів **@nitra/cursor**: **k8s** (Deployment + **HealthCheckPolicy** у **`hc.yaml`**, overlay **ua** / **ru** — **nodeSelector**, **HTTPRoute** (будь-який непорожній **`target.name`**), видалення **HealthCheckPolicy** у **ru**), гілки **dev**, **ua**, **ru** у **clean-merged-branch**, а також заборона артефактів **Firebase Hosting** у корені репозиторію.
|
|
7
|
+
Правило **abie** для споживачів **@nitra/cursor**: **k8s** (Deployment + **HealthCheckPolicy** у **`hc.yaml`**, overlay **ua** / **ru** — **nodeSelector**, **HTTPRoute** (будь-який непорожній **`target.name`**, для спільних сервісів **`auth-run-hl`** / **`filelint-hl`** — **`namespace: dev`** у base та patch **`…/backendRefs/…/namespace`** у **ua** / **ru**), видалення **HealthCheckPolicy** у **ru**), гілки **dev**, **ua**, **ru** у **clean-merged-branch**, а також заборона артефактів **Firebase Hosting** у корені репозиторію.
|
|
8
8
|
|
|
9
9
|
**`npx @nitra/cursor check abie`** виконується лише якщо в **`.n-cursor.json`** у **`rules`** є **`abie`** — інакше вихід **0** без зауважень.
|
|
10
10
|
|
|
@@ -12,7 +12,7 @@ version: '1.9'
|
|
|
12
12
|
|
|
13
13
|
## k8s: `hc.yaml` поруч із Deployment
|
|
14
14
|
|
|
15
|
-
Якщо під **`k8s`** є **Deployment**, у **тій самій директорії** має бути **`hc.yaml`** з **HealthCheckPolicy** (**`networking.gke.io/v1`**): коректний modeline **`$schema`**, **`/healthz`**, порт **8080**, **`targetRef.name`**
|
|
15
|
+
Якщо під **`k8s`** є **Deployment**, у **тій самій директорії** має бути **`hc.yaml`** з **HealthCheckPolicy** (**`networking.gke.io/v1`**): коректний modeline **`$schema`**, **`/healthz`**, порт **8080**, **`targetRef.name`** — **headless** **Service** з суфіксом **`-hl`** (узгоджено з парою **`svc.yaml`** / **`svc-hl.yaml`** у **k8s.mdc**): або **`${metadata.name}-hl`**, або те саме ім’я, якщо **`metadata.name`** уже з **`-hl`**.
|
|
16
16
|
|
|
17
17
|
```yaml title="hc.yaml"
|
|
18
18
|
# yaml-language-server: $schema=https://datreeio.github.io/CRDs-catalog/networking.gke.io/healthcheckpolicy_v1.json
|
|
@@ -31,13 +31,35 @@ spec:
|
|
|
31
31
|
targetRef:
|
|
32
32
|
group: ''
|
|
33
33
|
kind: Service
|
|
34
|
-
name:
|
|
34
|
+
name: СЕРВІС-hl
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
## k8s: overlay **HTTPRoute** (**ua** / **ru**)
|
|
38
38
|
|
|
39
39
|
За наявності **Deployment** під **k8s** і наявності **Vite** (**`vite.config.js`**, **`vite.config.mjs`** або **`vite.config.ts`** у каталозі пакета) у **`ua/kustomization.yaml`** та **`ru/kustomization.yaml`** цього пакета потрібні **inline JSON6902** у **`patches`**: **target** **`kind: HTTPRoute`**, **непорожній `name`** (як у маніфесті маршруту). Мають бути зміни **`/spec/hostnames`** (домени abie — у скрипті) та **`/spec/parentRefs/0/namespace`** (**`ua`** / **`ru`**). Для **ru** — анотація **`gwin.yandex.cloud/rules.http.upgradeTypes: websocket`** лише якщо в **тому ж** **`ru/kustomization.yaml`** є згадка **`HASURA_GRAPHQL_JWT_SECRET`** (типово patch на **ConfigMap** Hasura). Як обирати **`op`** (**add** / **replace** тощо) у patch — **k8s.mdc** (розділ про JSON patch у kustomization).
|
|
40
40
|
|
|
41
|
+
### HTTPRoute: спільні сервіси **`auth-run-hl`**, **`filelint-hl`**
|
|
42
|
+
|
|
43
|
+
Ці **Service** (headless **`-hl`**) живуть у **базовому** неймспейсі **`dev`**. У маніфесті **HTTPRoute** під **`k8s`** (шар без **`ua/`** та **`ru/`** — наприклад **`…/k8s/base/hr.yaml`**) для кожного **`backendRefs`** до такого сервісу явно вкажи **`namespace: dev`** і порт **8080**:
|
|
44
|
+
|
|
45
|
+
```yaml title="…/k8s/base/hr.yaml (фрагмент)"
|
|
46
|
+
spec:
|
|
47
|
+
rules:
|
|
48
|
+
- matches:
|
|
49
|
+
- path:
|
|
50
|
+
type: PathPrefix
|
|
51
|
+
value: /
|
|
52
|
+
backendRefs:
|
|
53
|
+
- name: auth-run-hl
|
|
54
|
+
namespace: dev
|
|
55
|
+
port: 8080
|
|
56
|
+
- name: filelint-hl
|
|
57
|
+
namespace: dev
|
|
58
|
+
port: 8080
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
У **`ua/kustomization.yaml`** та **`ru/kustomization.yaml`** додай до того самого **inline** patch на **`HTTPRoute`** (той самий **`target.name`**) операції **JSON6902** з **`path`**: **`/spec/rules/<i>/backendRefs/<j>/namespace`**, де **`<i>`** / **`<j>`** — індекси відповідно до порядку **`spec.rules`** та **`backendRefs`** у base-файлі; **`value`**: **`ua`** або **`ru`**. Якщо кілька таких **`backendRefs`**, потрібна окрема операція для кожного.
|
|
62
|
+
|
|
41
63
|
```yaml title="…/ua/kustomization.yaml (фрагмент)"
|
|
42
64
|
- target:
|
|
43
65
|
kind: HTTPRoute
|
|
@@ -50,6 +72,12 @@ spec:
|
|
|
50
72
|
- op: replace
|
|
51
73
|
path: /spec/parentRefs/0/namespace
|
|
52
74
|
value: ua
|
|
75
|
+
- op: replace
|
|
76
|
+
path: /spec/rules/0/backendRefs/0/namespace
|
|
77
|
+
value: ua
|
|
78
|
+
- op: replace
|
|
79
|
+
path: /spec/rules/0/backendRefs/1/namespace
|
|
80
|
+
value: ua
|
|
53
81
|
```
|
|
54
82
|
|
|
55
83
|
```yaml title="…/ru/kustomization.yaml (фрагмент)"
|
|
@@ -64,6 +92,12 @@ spec:
|
|
|
64
92
|
- op: replace
|
|
65
93
|
path: /spec/parentRefs/0/namespace
|
|
66
94
|
value: ru
|
|
95
|
+
- op: replace
|
|
96
|
+
path: /spec/rules/0/backendRefs/0/namespace
|
|
97
|
+
value: ru
|
|
98
|
+
- op: replace
|
|
99
|
+
path: /spec/rules/0/backendRefs/1/namespace
|
|
100
|
+
value: ru
|
|
67
101
|
```
|
|
68
102
|
|
|
69
103
|
Якщо в цьому ж файлі є **`HASURA_GRAPHQL_JWT_SECRET`** (Hasura з JWT), додай окремий patch на **HTTPRoute** з анотацією для WebSocket:
|
package/mdc/k8s.mdc
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: K8s YAML — $schema (yaml-language-server); lint-k8s (kubeconform, kubescape); check-k8s
|
|
3
|
-
version: '1.
|
|
3
|
+
version: '1.25'
|
|
4
4
|
globs: "**/k8s/**/*.yaml"
|
|
5
5
|
alwaysApply: false
|
|
6
6
|
---
|
|
@@ -253,44 +253,13 @@ spec:
|
|
|
253
253
|
|
|
254
254
|
Для `$schema` у першому рядку див. приклад **HealthCheckPolicy** у тому ж розділі (datree CRDs-catalog).
|
|
255
255
|
|
|
256
|
+
**`spec.targetRef`** (типово **`kind: Service`**) має вказувати на **headless** сервіс — ім’я з суфіксом **`-hl`** (див. **«Service: `svc.yaml` і `svc-hl.yaml`»**); для проєктів **abie** точні умови — **`check-abie.mjs`** / **abie.mdc**.
|
|
257
|
+
|
|
256
258
|
За потреби розшир **`target`** (`name`, `namespace`), щоб однозначно вказати об’єкт.
|
|
257
259
|
|
|
258
260
|
**`check k8s`:** заборонено **`kind: Ingress`**.
|
|
259
261
|
|
|
260
|
-
3.
|
|
261
|
-
|
|
262
|
-
```yaml title="base/rg.yaml"
|
|
263
|
-
# yaml-language-server: $schema=https://datreeio.github.io/CRDs-catalog/gateway.networking.k8s.io/referencegrant_v1beta1.json
|
|
264
|
-
apiVersion: gateway.networking.k8s.io/v1beta1
|
|
265
|
-
kind: ReferenceGrant
|
|
266
|
-
metadata:
|
|
267
|
-
name: contract-to-dev
|
|
268
|
-
namespace: dev
|
|
269
|
-
spec:
|
|
270
|
-
from:
|
|
271
|
-
- group: gateway.networking.k8s.io
|
|
272
|
-
kind: HTTPRoute
|
|
273
|
-
namespace: contract
|
|
274
|
-
to:
|
|
275
|
-
- group: ''
|
|
276
|
-
kind: Service
|
|
277
|
-
# Якщо name не вказано, доступ дозволено до всіх Service в цьому namespace
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
**ReferenceGrant** має бути в **namespace** тих **Service**, до яких відкривається доступ (у прикладі — **`dev`**). Якщо **`namespace:`** у **overlay** не збігається з **namespace** гранта, **не** додавай **`base/rg.yaml`** у **`resources:`** того overlay — Kustomize може перезаписати **`metadata.namespace`** гранта. Натомість застосуй **`patches`** (JSON patch), щоб явно виставити потрібний **namespace**:
|
|
281
|
-
|
|
282
|
-
```yaml title="overlay/kustomization.yaml (фрагмент)"
|
|
283
|
-
patches:
|
|
284
|
-
- target:
|
|
285
|
-
kind: ReferenceGrant
|
|
286
|
-
name: contract-to-dev
|
|
287
|
-
patch: |-
|
|
288
|
-
- op: replace
|
|
289
|
-
path: /metadata/namespace
|
|
290
|
-
value: dev
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
4. **JSON patch у kustomization:** де можливо, змінюй ресурс через **`op: replace`** (одна операція на `path`), а не пару **`remove` + `add`** на той самий шлях. **`add`** / **`remove`** лишай лише коли **`replace`** не підходить (наприклад додати новий ключ або прибрати поле без заміни).
|
|
262
|
+
3. **JSON patch у kustomization:** де можливо, змінюй ресурс через **`op: replace`** (одна операція на `path`), а не пару **`remove` + `add`** на той самий шлях. **`add`** / **`remove`** лишай лише коли **`replace`** не підходить (наприклад додати новий ключ або прибрати поле без заміни).
|
|
294
263
|
|
|
295
264
|
```yaml title="overlay/kustomization.yaml (фрагмент)"
|
|
296
265
|
patches:
|
|
@@ -308,7 +277,7 @@ patches:
|
|
|
308
277
|
|
|
309
278
|
**`npx @nitra/cursor check k8s`** — програмні критерії в **JSDoc на початку** **`npm/scripts/check-k8s.mjs`**. Якщо під **`k8s`** немає **`*.yaml`** — крок пропущено. Канон **`$schema`** для редактора — розділ **«Визначення схеми YAML`** нижче.
|
|
310
279
|
|
|
311
|
-
**Не входить у check k8s:** наприклад
|
|
280
|
+
**Не входить у check k8s:** наприклад повна структура **`HTTPRoute`** для **Hasura** (канон — у розділі про **`hasura/graphql-engine`**), **kubeconform** / **kubescape** — це **`bun run lint-k8s`**.
|
|
312
281
|
|
|
313
282
|
## Коли застосовувати (агентам)
|
|
314
283
|
|
package/package.json
CHANGED
package/scripts/check-abie.mjs
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
*
|
|
13
13
|
* **k8s:** якщо під деревом із сегментом **`k8s`** є YAML з **`kind: Deployment`**, у тій самій директорії
|
|
14
14
|
* має існувати **`hc.yaml`** із **`HealthCheckPolicy`** (**`networking.gke.io/v1`**), modeline **`$schema`**
|
|
15
|
-
* як у abie.mdc, **`/healthz`**, порт **8080**, **`targetRef`** на **Service** з
|
|
15
|
+
* як у abie.mdc, **`/healthz`**, порт **8080**, **`targetRef`** на **headless Service** (ім’я з суфіксом **`-hl`**):
|
|
16
|
+
* якщо **`metadata.name`** уже закінчується на **`-hl`**, **`targetRef.name`** має збігатися з ним; інакше **`targetRef.name`** = **`${metadata.name}-hl`**.
|
|
16
17
|
* Загальні вимоги до **`# yaml-language-server: $schema`** для інших YAML під **`k8s`** — у **check-k8s.mjs** / **k8s.mdc** (наприклад **HttpBackendGroup** `alb.yc.io/v1alpha1` — **без** modeline).
|
|
17
18
|
* Якщо в дереві **k8s** є **HealthCheckPolicy**, перевіряється **`ru/kustomization.yaml`** з patch **`$patch: delete`**
|
|
18
19
|
* (логіка вмісту — **`ruKustomizationHasHealthCheckDeletePatch`** у **check-k8s.mjs**, узгоджено з **k8s.mdc**).
|
|
@@ -28,6 +29,8 @@
|
|
|
28
29
|
* — тоді в **`ua`/`ru` kustomization** потрібен patch на **`kind: HTTPRoute`**, **непорожній `target.name`**: **`/spec/hostnames`**
|
|
29
30
|
* (домени abie.mdc), **`/spec/parentRefs/0/namespace`** (**ua** / **ru**); для **ru** — **`gwin.yandex.cloud/rules.http.upgradeTypes: websocket`**,
|
|
30
31
|
* якщо в тому ж **`kustomization.yaml`** згадується **`HASURA_GRAPHQL_JWT_SECRET`** (Hasura + JWT).
|
|
32
|
+
* **Спільні бекенди (`auth-run-hl`, `filelint-hl`):** у **HTTPRoute** під **`k8s`** поза overlay **ua** та **ru** (шлях не містить **`k8s/ua/`** чи **`k8s/ru/`**) кожен такий **`backendRefs`** має **`namespace: dev`** і порт **8080**;
|
|
33
|
+
* у patch overlay **ua** та **ru** — по одному **JSON6902** на **`/spec/rules/…/backendRefs/…/namespace`** з **`value`**: **ua** або **ru** (кількість patch-ів = кількість таких **`backendRefs`** у пакеті).
|
|
31
34
|
* Вибір **`op`** — **k8s.mdc**.
|
|
32
35
|
*/
|
|
33
36
|
import { existsSync } from 'node:fs'
|
|
@@ -46,6 +49,14 @@ const CONFIG_FILE = '.n-cursor.json'
|
|
|
46
49
|
/** Маркер у kustomization.yaml: якщо зустрічається у файлі — для overlay ru у patch HTTPRoute потрібна анотація gwin…websocket. */
|
|
47
50
|
const HASURA_JWT_SECRET_IN_KUSTOMIZATION = 'HASURA_GRAPHQL_JWT_SECRET'
|
|
48
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Спільні **Service** (**`-hl`**) у **dev**: у base-**HTTPRoute** обов’язково **`namespace: dev`**, у overlay — patch **`…/backendRefs/…/namespace`** (abie.mdc).
|
|
54
|
+
* Експорт для споживачів / тестів.
|
|
55
|
+
*/
|
|
56
|
+
export const ABIE_SHARED_CROSS_NS_BACKEND_NAMES = Object.freeze(['auth-run-hl', 'filelint-hl'])
|
|
57
|
+
|
|
58
|
+
const ABIE_SHARED_CROSS_NS_BACKEND_SET = new Set(ABIE_SHARED_CROSS_NS_BACKEND_NAMES)
|
|
59
|
+
|
|
49
60
|
/** Очікуваний URL **`$schema`** для **hc.yaml** (abie.mdc). */
|
|
50
61
|
export const ABIE_HC_SCHEMA_URL = 'https://datreeio.github.io/CRDs-catalog/networking.gke.io/healthcheckpolicy_v1.json'
|
|
51
62
|
|
|
@@ -528,6 +539,136 @@ export function kustomizationHasAbieDeploymentNodeSelectorPatch(raw, mode) {
|
|
|
528
539
|
return false
|
|
529
540
|
}
|
|
530
541
|
|
|
542
|
+
/**
|
|
543
|
+
* Чи YAML відносно кореня належить до **`${pkgRel}/k8s/**`** поза піддеревами **`ua/`** та **`ru/`** (base-шар abie).
|
|
544
|
+
* @param {string} relFromRoot відносний шлях від кореня
|
|
545
|
+
* @param {string} pkgRelFromRoot каталог пакета відносно кореня (без завершального слеша після імені пакета)
|
|
546
|
+
* @returns {boolean}
|
|
547
|
+
*/
|
|
548
|
+
export function isK8sYamlInAbiePackageExcludingUaRuOverlays(relFromRoot, pkgRelFromRoot) {
|
|
549
|
+
const normRel = relFromRoot.replaceAll('\\', '/')
|
|
550
|
+
const pkg = pkgRelFromRoot.replaceAll('\\', '/').replace(/\/$/u, '')
|
|
551
|
+
const prefix = `${pkg}/k8s/`
|
|
552
|
+
if (!normRel.startsWith(prefix)) {
|
|
553
|
+
return false
|
|
554
|
+
}
|
|
555
|
+
const after = normRel.slice(prefix.length)
|
|
556
|
+
return !after.startsWith('ua/') && !after.startsWith('ru/')
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* З HTTPRoute-документа рахує **`backendRefs`** до **`auth-run-hl`** / **`filelint-hl`** і порушення **`namespace: dev`**.
|
|
561
|
+
* @param {unknown} obj корінь YAML
|
|
562
|
+
* @param {string} rel відносний шлях (повідомлення)
|
|
563
|
+
* @returns {{ refCount: number, errors: string[] }}
|
|
564
|
+
*/
|
|
565
|
+
function httpRouteDocSharedCrossNsBackendStats(obj, rel) {
|
|
566
|
+
/** @type {string[]} */
|
|
567
|
+
const errors = []
|
|
568
|
+
if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
|
|
569
|
+
return { refCount: 0, errors }
|
|
570
|
+
}
|
|
571
|
+
const rec = /** @type {Record<string, unknown>} */ (obj)
|
|
572
|
+
if (rec.kind !== 'HTTPRoute') {
|
|
573
|
+
return { refCount: 0, errors }
|
|
574
|
+
}
|
|
575
|
+
const spec = rec.spec
|
|
576
|
+
if (spec === null || typeof spec !== 'object' || Array.isArray(spec)) {
|
|
577
|
+
return { refCount: 0, errors }
|
|
578
|
+
}
|
|
579
|
+
const rules = /** @type {Record<string, unknown>} */ (spec).rules
|
|
580
|
+
if (!Array.isArray(rules)) {
|
|
581
|
+
return { refCount: 0, errors }
|
|
582
|
+
}
|
|
583
|
+
let refCount = 0
|
|
584
|
+
for (const rule of rules) {
|
|
585
|
+
if (rule === null || typeof rule !== 'object' || Array.isArray(rule)) {
|
|
586
|
+
continue
|
|
587
|
+
}
|
|
588
|
+
const brs = /** @type {Record<string, unknown>} */ (rule).backendRefs
|
|
589
|
+
if (!Array.isArray(brs)) {
|
|
590
|
+
continue
|
|
591
|
+
}
|
|
592
|
+
for (const br of brs) {
|
|
593
|
+
if (br === null || typeof br !== 'object' || Array.isArray(br)) {
|
|
594
|
+
continue
|
|
595
|
+
}
|
|
596
|
+
const brRec = /** @type {Record<string, unknown>} */ (br)
|
|
597
|
+
const name = brRec.name
|
|
598
|
+
if (typeof name !== 'string' || !ABIE_SHARED_CROSS_NS_BACKEND_SET.has(name)) {
|
|
599
|
+
continue
|
|
600
|
+
}
|
|
601
|
+
refCount++
|
|
602
|
+
const ns = brRec.namespace
|
|
603
|
+
if (typeof ns !== 'string' || ns !== 'dev') {
|
|
604
|
+
errors.push(`${rel}: HTTPRoute backendRefs до ${name} має містити namespace: dev (abie.mdc)`)
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return { refCount, errors }
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* З YAML під **k8s** пакета (без overlay **ua** та **ru**) збирає кількість **`backendRefs`** до **`auth-run-hl`** і **`filelint-hl`** і порушення **`namespace: dev`**.
|
|
613
|
+
* @param {string} root корінь репозиторію
|
|
614
|
+
* @param {string} pkgAbs абсолютний шлях до каталогу пакета
|
|
615
|
+
* @param {string[]} yamlFilesAbs усі **yaml** під **k8s** (як **findK8sYamlFiles**)
|
|
616
|
+
* @returns {Promise<{ refCount: number, baseErrors: string[] }>}
|
|
617
|
+
*/
|
|
618
|
+
export async function analyzeAbieSharedBackendRefsInPackageK8s(root, pkgAbs, yamlFilesAbs) {
|
|
619
|
+
const pkgRel = relative(root, pkgAbs).replaceAll('\\', '/') || pkgAbs
|
|
620
|
+
let refCount = 0
|
|
621
|
+
/** @type {string[]} */
|
|
622
|
+
const baseErrors = []
|
|
623
|
+
for (const abs of yamlFilesAbs) {
|
|
624
|
+
const rel = relative(root, abs).replaceAll('\\', '/') || abs
|
|
625
|
+
if (!isK8sYamlInAbiePackageExcludingUaRuOverlays(rel, pkgRel)) {
|
|
626
|
+
continue
|
|
627
|
+
}
|
|
628
|
+
let raw
|
|
629
|
+
try {
|
|
630
|
+
raw = await readFile(abs, 'utf8')
|
|
631
|
+
} catch {
|
|
632
|
+
continue
|
|
633
|
+
}
|
|
634
|
+
const body = stripBom(raw)
|
|
635
|
+
const lines = body.split(/\r?\n/u)
|
|
636
|
+
const first = lines[0] ?? ''
|
|
637
|
+
const rest = MODELINE_RE.test(first.trim()) ? lines.slice(1).join('\n') : body
|
|
638
|
+
/** @type {import('yaml').Document[]} */
|
|
639
|
+
let docs
|
|
640
|
+
try {
|
|
641
|
+
docs = parseAllDocuments(rest)
|
|
642
|
+
} catch {
|
|
643
|
+
continue
|
|
644
|
+
}
|
|
645
|
+
for (const doc of docs) {
|
|
646
|
+
if (doc.errors.length > 0) {
|
|
647
|
+
continue
|
|
648
|
+
}
|
|
649
|
+
const obj = doc.toJSON()
|
|
650
|
+
const st = httpRouteDocSharedCrossNsBackendStats(obj, rel)
|
|
651
|
+
refCount += st.refCount
|
|
652
|
+
baseErrors.push(...st.errors)
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return { refCount, baseErrors }
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Рахує операції JSON6902 з **`path`**: **`/spec/rules/…/backendRefs/…/namespace`** та **`value`** overlay.
|
|
660
|
+
* @param {string} combined сукупний текст patch **HTTPRoute**
|
|
661
|
+
* @param {'ua' | 'ru'} mode overlay
|
|
662
|
+
* @returns {number}
|
|
663
|
+
*/
|
|
664
|
+
function countAbieHttpRouteBackendRefNamespacePatchesInCombined(combined, mode) {
|
|
665
|
+
const re =
|
|
666
|
+
mode === 'ua'
|
|
667
|
+
? /path:\s*\/spec\/rules\/\d+\/backendRefs\/\d+\/namespace\b[\s\S]{0,200}?value:\s*['"]?ua['"]?(?:\s|$)/gmu
|
|
668
|
+
: /path:\s*\/spec\/rules\/\d+\/backendRefs\/\d+\/namespace\b[\s\S]{0,200}?value:\s*['"]?ru['"]?(?:\s|$)/gmu
|
|
669
|
+
return [...combined.matchAll(re)].length
|
|
670
|
+
}
|
|
671
|
+
|
|
531
672
|
/** Домени **hostnames** для overlay **ua** (підрядки у JSON6902-тексті patch), abie.mdc. */
|
|
532
673
|
const ABIE_UA_HTTPROUTE_HOST_MARKERS = ['abie.app', 'vybeerai.com.ua', '*.abie.app', '*.vybeerai.com.ua']
|
|
533
674
|
|
|
@@ -610,9 +751,15 @@ export function getCombinedNginxRunPatchTextFromKustomization(raw) {
|
|
|
610
751
|
* @param {string} combined текст одного або кількох inline **patch**, розділених символом нового рядка
|
|
611
752
|
* @param {'ua' | 'ru'} mode **ua** або **ru**
|
|
612
753
|
* @param {string} [fullKustomizationRaw] повний текст **kustomization.yaml** — для **ru** визначає, чи потрібна анотація **gwin…websocket** (лише якщо є **`HASURA_GRAPHQL_JWT_SECRET`**)
|
|
754
|
+
* @param {number} [sharedCrossNsBackendRefCount] скільки **`backendRefs`** до **`auth-run-hl`** і **`filelint-hl`** у base **HTTPRoute** пакета — стільки ж patch-ів **`…/backendRefs/…/namespace`** з **`value`** overlay
|
|
613
755
|
* @returns {string | null} повідомлення про помилку або **null**
|
|
614
756
|
*/
|
|
615
|
-
export function validateAbieNginxRunHttpRoutePatches(
|
|
757
|
+
export function validateAbieNginxRunHttpRoutePatches(
|
|
758
|
+
combined,
|
|
759
|
+
mode,
|
|
760
|
+
fullKustomizationRaw,
|
|
761
|
+
sharedCrossNsBackendRefCount = 0
|
|
762
|
+
) {
|
|
616
763
|
if (typeof combined !== 'string' || combined.trim() === '') {
|
|
617
764
|
return `очікується patch target kind HTTPRoute з непорожнім target.name (hostnames, parentRefs namespace ${mode}; для ru — gwin… websocket лише за наявності HASURA_GRAPHQL_JWT_SECRET у файлі) — abie.mdc`
|
|
618
765
|
}
|
|
@@ -637,6 +784,16 @@ export function validateAbieNginxRunHttpRoutePatches(combined, mode, fullKustomi
|
|
|
637
784
|
if (ruNeedsWebsocket && !/gwin\.yandex\.cloud\/rules\.http\.upgradeTypes:\s*['"]?websocket['"]?/m.test(combined)) {
|
|
638
785
|
return 'HTTPRoute (ru): за наявності HASURA_GRAPHQL_JWT_SECRET у kustomization потрібна анотація gwin.yandex.cloud/rules.http.upgradeTypes: websocket (abie.mdc)'
|
|
639
786
|
}
|
|
787
|
+
const sharedCount =
|
|
788
|
+
typeof sharedCrossNsBackendRefCount === 'number' && Number.isFinite(sharedCrossNsBackendRefCount)
|
|
789
|
+
? Math.max(0, Math.floor(sharedCrossNsBackendRefCount))
|
|
790
|
+
: 0
|
|
791
|
+
if (sharedCount > 0) {
|
|
792
|
+
const patchHits = countAbieHttpRouteBackendRefNamespacePatchesInCombined(combined, mode)
|
|
793
|
+
if (patchHits < sharedCount) {
|
|
794
|
+
return `HTTPRoute: для backendRefs до спільних сервісів auth-run-hl, filelint-hl очікується ${sharedCount} JSON6902 patch(ів) з path /spec/rules/…/backendRefs/…/namespace та value ${mode} (зараз ${patchHits}) — abie.mdc`
|
|
795
|
+
}
|
|
796
|
+
}
|
|
640
797
|
return null
|
|
641
798
|
}
|
|
642
799
|
|
|
@@ -744,8 +901,9 @@ export function validateAbieHcYaml(raw, relPath) {
|
|
|
744
901
|
return `${relPath}: targetRef.kind має бути Service (abie.mdc)`
|
|
745
902
|
}
|
|
746
903
|
const svcName = targetRef.name
|
|
747
|
-
|
|
748
|
-
|
|
904
|
+
const expectedHl = name.endsWith('-hl') ? name : `${name}-hl`
|
|
905
|
+
if (typeof svcName !== 'string' || svcName !== expectedHl) {
|
|
906
|
+
return `${relPath}: targetRef.name має посилатися на headless Service (очікується ${expectedHl}, суфікс -hl) (abie.mdc)`
|
|
749
907
|
}
|
|
750
908
|
return null
|
|
751
909
|
}
|
|
@@ -912,6 +1070,21 @@ async function ensureUaRuAbieNodeSelectorPatches(root, yamlFilesAbs, deploymentD
|
|
|
912
1070
|
* @returns {Promise<void>}
|
|
913
1071
|
*/
|
|
914
1072
|
async function ensureUaRuAbieHttpRoutePatches(root, yamlFilesAbs, fail, passFn) {
|
|
1073
|
+
/** @type {Map<string, Promise<{ refCount: number, baseErrors: string[] }>>} */
|
|
1074
|
+
const sharedBackendAnalysisByPkg = new Map()
|
|
1075
|
+
/**
|
|
1076
|
+
* @param {string} pkgAbs
|
|
1077
|
+
* @returns {Promise<{ refCount: number, baseErrors: string[] }>}
|
|
1078
|
+
*/
|
|
1079
|
+
const getSharedBackendAnalysis = pkgAbs => {
|
|
1080
|
+
let p = sharedBackendAnalysisByPkg.get(pkgAbs)
|
|
1081
|
+
if (!p) {
|
|
1082
|
+
p = analyzeAbieSharedBackendRefsInPackageK8s(root, pkgAbs, yamlFilesAbs)
|
|
1083
|
+
sharedBackendAnalysisByPkg.set(pkgAbs, p)
|
|
1084
|
+
}
|
|
1085
|
+
return p
|
|
1086
|
+
}
|
|
1087
|
+
|
|
915
1088
|
const uaAbsList = yamlFilesAbs.filter(abs => isUaKustomizationPath(relative(root, abs).replaceAll('\\', '/') || abs))
|
|
916
1089
|
if (uaAbsList.length === 0) {
|
|
917
1090
|
passFn(
|
|
@@ -921,6 +1094,16 @@ async function ensureUaRuAbieHttpRoutePatches(root, yamlFilesAbs, fail, passFn)
|
|
|
921
1094
|
for (const abs of uaAbsList) {
|
|
922
1095
|
const rel = relative(root, abs).replaceAll('\\', '/') || abs
|
|
923
1096
|
if (abieOverlayRequiresHttpRouteByVite(root, abs)) {
|
|
1097
|
+
const pkgAbs = abiePackageDirFromK8sOverlay(root, abs)
|
|
1098
|
+
if (!pkgAbs) {
|
|
1099
|
+
fail(`${rel}: внутрішня помилка abie overlay (немає каталогу пакета)`)
|
|
1100
|
+
return
|
|
1101
|
+
}
|
|
1102
|
+
const sharedAnalysis = await getSharedBackendAnalysis(pkgAbs)
|
|
1103
|
+
for (const err of sharedAnalysis.baseErrors) {
|
|
1104
|
+
fail(err)
|
|
1105
|
+
return
|
|
1106
|
+
}
|
|
924
1107
|
let raw
|
|
925
1108
|
try {
|
|
926
1109
|
raw = await readFile(abs, 'utf8')
|
|
@@ -930,7 +1113,7 @@ async function ensureUaRuAbieHttpRoutePatches(root, yamlFilesAbs, fail, passFn)
|
|
|
930
1113
|
return
|
|
931
1114
|
}
|
|
932
1115
|
const combined = getCombinedNginxRunPatchTextFromKustomization(raw)
|
|
933
|
-
const v = validateAbieNginxRunHttpRoutePatches(combined, 'ua')
|
|
1116
|
+
const v = validateAbieNginxRunHttpRoutePatches(combined, 'ua', raw, sharedAnalysis.refCount)
|
|
934
1117
|
if (v !== null) {
|
|
935
1118
|
fail(`${rel}: ${v}`)
|
|
936
1119
|
return
|
|
@@ -950,6 +1133,16 @@ async function ensureUaRuAbieHttpRoutePatches(root, yamlFilesAbs, fail, passFn)
|
|
|
950
1133
|
for (const abs of ruAbsList) {
|
|
951
1134
|
const rel = relative(root, abs).replaceAll('\\', '/') || abs
|
|
952
1135
|
if (abieOverlayRequiresHttpRouteByVite(root, abs)) {
|
|
1136
|
+
const pkgAbs = abiePackageDirFromK8sOverlay(root, abs)
|
|
1137
|
+
if (!pkgAbs) {
|
|
1138
|
+
fail(`${rel}: внутрішня помилка abie overlay (немає каталогу пакета)`)
|
|
1139
|
+
return
|
|
1140
|
+
}
|
|
1141
|
+
const sharedAnalysis = await getSharedBackendAnalysis(pkgAbs)
|
|
1142
|
+
for (const err of sharedAnalysis.baseErrors) {
|
|
1143
|
+
fail(err)
|
|
1144
|
+
return
|
|
1145
|
+
}
|
|
953
1146
|
let raw
|
|
954
1147
|
try {
|
|
955
1148
|
raw = await readFile(abs, 'utf8')
|
|
@@ -959,7 +1152,7 @@ async function ensureUaRuAbieHttpRoutePatches(root, yamlFilesAbs, fail, passFn)
|
|
|
959
1152
|
return
|
|
960
1153
|
}
|
|
961
1154
|
const combined = getCombinedNginxRunPatchTextFromKustomization(raw)
|
|
962
|
-
const v = validateAbieNginxRunHttpRoutePatches(combined, 'ru', raw)
|
|
1155
|
+
const v = validateAbieNginxRunHttpRoutePatches(combined, 'ru', raw, sharedAnalysis.refCount)
|
|
963
1156
|
if (v !== null) {
|
|
964
1157
|
fail(`${rel}: ${v}`)
|
|
965
1158
|
return
|
package/scripts/check-k8s.mjs
CHANGED
|
@@ -1148,6 +1148,9 @@ export function serviceSvcHlYamlHeadlessViolation(manifest) {
|
|
|
1148
1148
|
|
|
1149
1149
|
/**
|
|
1150
1150
|
* Чи об’єкт схожий на **backendRef** до **Kubernetes Service** у Gateway API.
|
|
1151
|
+
*
|
|
1152
|
+
* Вимагає числовий **`port`**, щоб не плутати з **`HTTPHeaderMatch`** тощо (там теж є **`name`**, але без **`port`**).
|
|
1153
|
+
*
|
|
1151
1154
|
* @param {unknown} obj вузол у дереві **`spec`**
|
|
1152
1155
|
* @returns {boolean} true, якщо враховуємо поле **`name`** як посилання на Service
|
|
1153
1156
|
*/
|
|
@@ -1155,6 +1158,7 @@ function isGatewayApiBackendRefToService(obj) {
|
|
|
1155
1158
|
if (obj === null || obj === undefined || typeof obj !== 'object' || Array.isArray(obj)) return false
|
|
1156
1159
|
const o = /** @type {Record<string, unknown>} */ (obj)
|
|
1157
1160
|
if (typeof o.name !== 'string') return false
|
|
1161
|
+
if (typeof o.port !== 'number') return false
|
|
1158
1162
|
const kind = o.kind
|
|
1159
1163
|
if (kind !== undefined && kind !== 'Service') return false
|
|
1160
1164
|
const group = o.group
|