@nitra/cursor 1.8.64 → 1.8.71
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 +103 -18
- package/package.json +1 -1
- package/scripts/check-abie.mjs +533 -4
- package/scripts/check-k8s.mjs +0 -10
package/mdc/abie.mdc
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Правила для проєктів
|
|
2
|
+
description: Правила для проєктів AbInBev Efes
|
|
3
3
|
alwaysApply: true
|
|
4
|
-
version: '1.
|
|
4
|
+
version: '1.4'
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Правило **abie** для споживачів **@nitra/cursor**: **k8s** (**Deployment** + **HealthCheckPolicy**), overlay **ua** / **ru** (**nodeSelector**, видалення **HealthCheckPolicy** у **ru**) і гілки в **clean-merged-branch**. **`npx @nitra/cursor check abie`** виконується лише якщо в **`.n-cursor.json`** у **`rules`** є **`abie`** — інакше вихід **0** без зауважень.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## k8s: `hc.yaml` поруч із Deployment
|
|
10
|
+
|
|
11
|
+
Якщо під **`k8s`** є **`kind: Deployment`**, у **тій самій директорії** має бути **`hc.yaml`** з **HealthCheckPolicy** (**`networking.gke.io/v1`**): коректний modeline **`$schema`**, **`/healthz`**, порт **8080**, **`targetRef.name`** = **`metadata.name`**. Деталі — **`validateAbieHcYaml`** у **`npm/scripts/check-abie.mjs`**.
|
|
10
12
|
|
|
11
13
|
```yaml title="hc.yaml"
|
|
12
14
|
# yaml-language-server: $schema=https://datreeio.github.io/CRDs-catalog/networking.gke.io/healthcheckpolicy_v1.json
|
|
13
15
|
apiVersion: networking.gke.io/v1
|
|
14
16
|
kind: HealthCheckPolicy
|
|
15
17
|
metadata:
|
|
16
|
-
name:
|
|
17
|
-
namespace: dev #
|
|
18
|
+
name: СЕРВІС
|
|
19
|
+
namespace: dev # kustomize overlay
|
|
18
20
|
spec:
|
|
19
21
|
default:
|
|
20
22
|
config:
|
|
@@ -25,34 +27,117 @@ spec:
|
|
|
25
27
|
targetRef:
|
|
26
28
|
group: ''
|
|
27
29
|
kind: Service
|
|
28
|
-
name:
|
|
30
|
+
name: СЕРВІС
|
|
29
31
|
```
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
## k8s: overlay **HTTPRoute** `nginx-run` (**ua** / **ru**)
|
|
34
|
+
|
|
35
|
+
Якщо під **`k8s`** є **Deployment**, у **кожному** **`ua/kustomization.yaml`** та **`ru/kustomization.yaml`** має бути inline **JSON6902** у **`patches[].patch`** на **`target.kind: HTTPRoute`**, **`name: nginx-run`**: **replace** **`/spec/hostnames`** (допустимі домени — як у прикладах нижче) і **replace** **`/spec/parentRefs/0/namespace`** (**`ua`** або **`ru`**). Для **ru** додатково потрібна анотація **`gwin.yandex.cloud/rules.http.upgradeTypes: websocket`** (зазвичай **op: add**, **`/metadata/annotations`**). Критерії — **`validateAbieNginxRunHttpRoutePatches`** / **`getCombinedNginxRunPatchTextFromKustomization`** у **`npm/scripts/check-abie.mjs`**.
|
|
32
36
|
|
|
33
|
-
```yaml title="kustomization.yaml"
|
|
37
|
+
```yaml title="…/ua/kustomization.yaml (фрагмент)"
|
|
38
|
+
- target:
|
|
39
|
+
kind: HTTPRoute
|
|
40
|
+
name: nginx-run
|
|
41
|
+
patch: |-
|
|
42
|
+
- op: replace
|
|
43
|
+
path: /spec/hostnames
|
|
44
|
+
value:
|
|
45
|
+
- "abie.app" # також допускається vybeerai.com.ua, *.vybeerai.com.ua, *.abie.app
|
|
46
|
+
- op: replace
|
|
47
|
+
path: /spec/parentRefs/0/namespace
|
|
48
|
+
value: ua
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```yaml title="…/ru/kustomization.yaml (фрагмент)"
|
|
52
|
+
- target:
|
|
53
|
+
kind: HTTPRoute
|
|
54
|
+
name: nginx-run
|
|
55
|
+
patch: |-
|
|
56
|
+
- op: replace
|
|
57
|
+
path: /spec/hostnames
|
|
58
|
+
value:
|
|
59
|
+
- "napitkivmeste.tech" # також допускається выбирайонлайн.рф, *.napitkivmeste.tech, *.выбирайонлайн.рф
|
|
60
|
+
- op: replace
|
|
61
|
+
path: /spec/parentRefs/0/namespace
|
|
62
|
+
value: ru
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```yaml title="…/ru/kustomization.yaml (фрагмент)"
|
|
66
|
+
- target:
|
|
67
|
+
kind: HTTPRoute
|
|
68
|
+
name: nginx-run
|
|
69
|
+
patch: |-
|
|
70
|
+
- op: add
|
|
71
|
+
path: /metadata/annotations
|
|
72
|
+
value:
|
|
73
|
+
gwin.yandex.cloud/rules.http.upgradeTypes: "websocket"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## k8s: overlay **`ru`** і HealthCheckPolicy
|
|
77
|
+
|
|
78
|
+
Якщо в дереві **k8s** є **HealthCheckPolicy**, **check abie** вимагає **`ru/kustomization.yaml`** з patch **`$patch: delete`** для політики (узгоджено з **k8s.mdc** / **check-k8s**, **`ruKustomizationHasHealthCheckDeletePatch`** у **`npm/scripts/check-k8s.mjs`**). Підстав реальне ім’я замість **`СЕРВІС`**:
|
|
79
|
+
|
|
80
|
+
```yaml title="…/ru/kustomization.yaml (фрагмент)"
|
|
34
81
|
patches:
|
|
35
82
|
- target:
|
|
36
83
|
kind: HealthCheckPolicy
|
|
84
|
+
name: СЕРВІС
|
|
37
85
|
patch: |-
|
|
38
86
|
kind: HealthCheckPolicy
|
|
39
87
|
metadata:
|
|
40
|
-
name:
|
|
88
|
+
name: СЕРВІС
|
|
41
89
|
$patch: delete
|
|
42
90
|
```
|
|
43
91
|
|
|
44
|
-
##
|
|
92
|
+
## k8s: overlay **`ua`** / **`ru`** і nodeSelector
|
|
45
93
|
|
|
46
|
-
|
|
94
|
+
Якщо під **`k8s`** є **Deployment**, у **кожному** **`…/ua/kustomization.yaml`** та **`…/ru/kustomization.yaml`** має бути inline **JSON6902** у **`patches[].patch`** на **`target.kind: Deployment`** з **nodeSelector** за політикою abie (**ua** — **add** + **preem** false; **ru** — **replace** + **yandex.cloud/preemptible** false). Критерії збігу — **`kustomizationHasAbieDeploymentNodeSelectorPatch`** у **`npm/scripts/check-abie.mjs`**.
|
|
47
95
|
|
|
48
|
-
```yaml title="
|
|
49
|
-
|
|
96
|
+
```yaml title="…/ua/kustomization.yaml (фрагмент)"
|
|
97
|
+
patches:
|
|
98
|
+
- target:
|
|
99
|
+
kind: Deployment
|
|
100
|
+
name: my-app
|
|
101
|
+
patch: |-
|
|
102
|
+
- op: add
|
|
103
|
+
path: /spec/template/spec/nodeSelector
|
|
104
|
+
value:
|
|
105
|
+
preem: 'false'
|
|
50
106
|
```
|
|
51
107
|
|
|
52
|
-
|
|
108
|
+
```yaml title="…/ru/kustomization.yaml (фрагмент)"
|
|
109
|
+
patches:
|
|
110
|
+
- target:
|
|
111
|
+
kind: Deployment
|
|
112
|
+
name: my-app
|
|
113
|
+
patch: |-
|
|
114
|
+
- op: replace
|
|
115
|
+
path: /spec/template/spec/nodeSelector
|
|
116
|
+
value:
|
|
117
|
+
yandex.cloud/preemptible: "false"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Базовий Deployment (`…/base/`)
|
|
53
121
|
|
|
54
|
-
|
|
122
|
+
Якщо **Deployment** у YAML під **`k8s`** лежить у шляху з сегментом **`base`** (наприклад **`…/k8s/base/deploy.yaml`**), у **`spec.template.spec.nodeSelector`** має бути **`preem`** зі значенням **істинно** (**`true`** або рядок **`'true'`**) — overlay **ua** / **ru** підміняє селектор через kustomize. Деталі — **`deploymentDocumentHasAbieBasePreemNodeSelector`** / **`isAbieK8sBaseYamlPath`** у **`npm/scripts/check-abie.mjs`**.
|
|
55
123
|
|
|
56
|
-
|
|
124
|
+
```yaml title="…/base/deploy.yaml (фрагмент)"
|
|
125
|
+
spec:
|
|
126
|
+
template:
|
|
127
|
+
spec:
|
|
128
|
+
nodeSelector:
|
|
129
|
+
preem: 'true' # буде замінено через kustomize
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Git branches
|
|
133
|
+
|
|
134
|
+
У **`.github/workflows/clean-merged-branch.yml`** у кроці **`phpdocker-io/github-actions-delete-abandoned-branches`** значення **`with.ignore_branches`** має містити **dev**, **ua** та **ru** (разом з іншими гілками, якщо потрібно), наприклад:
|
|
135
|
+
|
|
136
|
+
```yaml title=".github/workflows/clean-merged-branch.yml (фрагмент)"
|
|
137
|
+
with:
|
|
138
|
+
ignore_branches: main,dev,ua,ru
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Перевірка
|
|
57
142
|
|
|
58
|
-
|
|
143
|
+
**`npx @nitra/cursor check abie`**
|
package/package.json
CHANGED
package/scripts/check-abie.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Перевіряє відповідність проєкту правилу abie.mdc (проєкти
|
|
2
|
+
* Перевіряє відповідність проєкту правилу abie.mdc (проєкти AbInBev Efes).
|
|
3
3
|
*
|
|
4
4
|
* Застосовується лише якщо у **`.n-cursor.json`** у масиві **`rules`** є **`abie`** — інакше вихід **0**
|
|
5
5
|
* без перевірок (щоб не суперечити типовому **ga.mdc** з **`ignore_branches: main,dev`**).
|
|
@@ -12,7 +12,18 @@
|
|
|
12
12
|
* має існувати **`hc.yaml`** із **`HealthCheckPolicy`** (**`networking.gke.io/v1`**), modeline **`$schema`**
|
|
13
13
|
* як у abie.mdc, **`/healthz`**, порт **8080**, **`targetRef`** на **Service** з тим самим **`metadata.name`**.
|
|
14
14
|
* Якщо в дереві **k8s** є **HealthCheckPolicy**, перевіряється **`ru/kustomization.yaml`** з patch **`$patch: delete`**
|
|
15
|
-
* (
|
|
15
|
+
* (логіка вмісту — **`ruKustomizationHasHealthCheckDeletePatch`** у **check-k8s.mjs**, узгоджено з **k8s.mdc**).
|
|
16
|
+
*
|
|
17
|
+
* **nodeSelector (base):** якщо **Deployment** лежить у шляху з сегментом **`base`** (наприклад **`…/k8s/base/deploy.yaml`**),
|
|
18
|
+
* у **`spec.template.spec.nodeSelector`** має бути **`preem`** з булевим значенням **true** або рядком **`'true'`** — overlay **ua** та **ru** далі підміняють селектор.
|
|
19
|
+
*
|
|
20
|
+
* **nodeSelector (overlay):** якщо є **Deployment** під **k8s**, у кожному **`ua/kustomization.yaml`** та **`ru/kustomization.yaml`**
|
|
21
|
+
* має бути inline **JSON6902** patch на **`kind: Deployment`**: для **ua** — **`op: add`**, **`path: /spec/template/spec/nodeSelector`**,
|
|
22
|
+
* **`preem: false`**; для **ru** — **`op: replace`**, той самий **path**, **`yandex.cloud/preemptible: false`** (див. abie.mdc).
|
|
23
|
+
*
|
|
24
|
+
* **HTTPRoute nginx-run:** за тієї ж умови (**Deployment** під **k8s**) у **кожному** **`ua`/`ru` kustomization** має бути
|
|
25
|
+
* inline **JSON6902** на **`kind: HTTPRoute`**, **`name: nginx-run`**: **replace** **`/spec/hostnames`** (домени з abie.mdc),
|
|
26
|
+
* **replace** **`/spec/parentRefs/0/namespace`** (**ua** / **ru**); для **ru** також **`gwin.yandex.cloud/rules.http.upgradeTypes: websocket`**.
|
|
16
27
|
*/
|
|
17
28
|
import { existsSync } from 'node:fs'
|
|
18
29
|
import { readFile } from 'node:fs/promises'
|
|
@@ -20,7 +31,7 @@ import { dirname, join, relative } from 'node:path'
|
|
|
20
31
|
|
|
21
32
|
import { parseAllDocuments } from 'yaml'
|
|
22
33
|
|
|
23
|
-
import {
|
|
34
|
+
import { pathHasK8sSegment, ruKustomizationHasHealthCheckDeletePatch } from './check-k8s.mjs'
|
|
24
35
|
import { pass } from './utils/pass.mjs'
|
|
25
36
|
import { flattenWorkflowSteps, getStepUses, parseWorkflowYaml } from './utils/gha-workflow.mjs'
|
|
26
37
|
import { walkDir } from './utils/walkDir.mjs'
|
|
@@ -35,6 +46,80 @@ const MODELINE_RE = /^#\s*yaml-language-server:\s*\$schema=(\S+)\s*$/
|
|
|
35
46
|
/** Гілки, які мають бути в **`ignore_branches`** за abie.mdc. */
|
|
36
47
|
export const ABIE_REQUIRED_IGNORE_BRANCHES = ['dev', 'ua', 'ru']
|
|
37
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Чи відносний шлях вказує на **`ru/kustomization.yaml`** (сегмент **`ru`** перед ім’ям файлу) — специфіка abie overlay.
|
|
51
|
+
* @param {string} rel шлях від кореня репозиторію
|
|
52
|
+
* @returns {boolean} true, якщо це `…/ru/kustomization.yaml`
|
|
53
|
+
*/
|
|
54
|
+
export function isRuKustomizationPath(rel) {
|
|
55
|
+
const norm = rel.replaceAll('\\', '/')
|
|
56
|
+
return /(^|\/)ru\/kustomization\.yaml$/u.test(norm)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Чи відносний шлях вказує на **`ua/kustomization.yaml`** (сегмент **`ua`** перед ім’ям файлу) — специфіка abie overlay.
|
|
61
|
+
* @param {string} rel шлях від кореня репозиторію
|
|
62
|
+
* @returns {boolean} true, якщо це `…/ua/kustomization.yaml`
|
|
63
|
+
*/
|
|
64
|
+
export function isUaKustomizationPath(rel) {
|
|
65
|
+
const norm = rel.replaceAll('\\', '/')
|
|
66
|
+
return /(^|\/)ua\/kustomization\.yaml$/u.test(norm)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Чи відносний шлях до YAML під **k8s** вказує на файл у каталозі **`base`** (сегмент **`base`** у шляху), abie.mdc.
|
|
71
|
+
* @param {string} rel шлях від кореня репозиторію
|
|
72
|
+
* @returns {boolean} true, якщо в шляху є **`/base/`**
|
|
73
|
+
*/
|
|
74
|
+
export function isAbieK8sBaseYamlPath(rel) {
|
|
75
|
+
const norm = rel.replaceAll('\\', '/')
|
|
76
|
+
return /(^|\/)base\//u.test(norm)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Чи значення **`preem`** у base **Deployment** вважається «істинним» за abie.mdc (**true** або рядок **`true`** без урахування регістру).
|
|
81
|
+
* @param {unknown} v значення з YAML
|
|
82
|
+
* @returns {boolean}
|
|
83
|
+
*/
|
|
84
|
+
function isAbiePreemTrueish(v) {
|
|
85
|
+
if (v === true) {
|
|
86
|
+
return true
|
|
87
|
+
}
|
|
88
|
+
if (typeof v === 'string' && v.trim().toLowerCase() === 'true') {
|
|
89
|
+
return true
|
|
90
|
+
}
|
|
91
|
+
return false
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Чи документ **Deployment** у **`…/base/…`** містить **`spec.template.spec.nodeSelector.preem`** зі значенням **true** (abie.mdc).
|
|
96
|
+
* @param {unknown} obj корінь YAML-документа (**Deployment**)
|
|
97
|
+
* @returns {boolean} true, якщо критерії виконано
|
|
98
|
+
*/
|
|
99
|
+
export function deploymentDocumentHasAbieBasePreemNodeSelector(obj) {
|
|
100
|
+
if (!isDeploymentDoc(obj)) {
|
|
101
|
+
return false
|
|
102
|
+
}
|
|
103
|
+
const rec = /** @type {Record<string, unknown>} */ (obj)
|
|
104
|
+
const spec = rec.spec
|
|
105
|
+
if (spec === null || typeof spec !== 'object' || Array.isArray(spec)) {
|
|
106
|
+
return false
|
|
107
|
+
}
|
|
108
|
+
const template = /** @type {Record<string, unknown>} */ (spec).template
|
|
109
|
+
if (template === null || typeof template !== 'object' || Array.isArray(template)) {
|
|
110
|
+
return false
|
|
111
|
+
}
|
|
112
|
+
const podSpec = /** @type {Record<string, unknown>} */ (template).spec
|
|
113
|
+
if (podSpec === null || typeof podSpec !== 'object' || Array.isArray(podSpec)) {
|
|
114
|
+
return false
|
|
115
|
+
}
|
|
116
|
+
const nodeSelector = /** @type {Record<string, unknown>} */ (podSpec).nodeSelector
|
|
117
|
+
if (nodeSelector === null || typeof nodeSelector !== 'object' || Array.isArray(nodeSelector)) {
|
|
118
|
+
return false
|
|
119
|
+
}
|
|
120
|
+
return isAbiePreemTrueish(nodeSelector.preem)
|
|
121
|
+
}
|
|
122
|
+
|
|
38
123
|
/**
|
|
39
124
|
* Чи увімкнено правило **abie** у конфігу репозиторію.
|
|
40
125
|
* @param {string} root корінь репозиторію (cwd)
|
|
@@ -189,6 +274,64 @@ async function collectDeploymentDirs(root, yamlAbs, fail) {
|
|
|
189
274
|
return dirs
|
|
190
275
|
}
|
|
191
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Для кожного **Deployment** у YAML під **`k8s`** з шляхом **`…/base/…`** вимагає **`spec.template.spec.nodeSelector.preem: true`** (abie.mdc).
|
|
279
|
+
* @param {string} root корінь репозиторію
|
|
280
|
+
* @param {string[]} yamlFilesAbs yaml під k8s
|
|
281
|
+
* @param {(msg: string) => void} fail callback
|
|
282
|
+
* @returns {Promise<void>}
|
|
283
|
+
*/
|
|
284
|
+
async function ensureAbieBaseDeploymentPreemNodeSelector(root, yamlFilesAbs, fail) {
|
|
285
|
+
const baseFiles = yamlFilesAbs.filter(abs => {
|
|
286
|
+
const rel = relative(root, abs).replaceAll('\\', '/') || abs
|
|
287
|
+
return isAbieK8sBaseYamlPath(rel)
|
|
288
|
+
})
|
|
289
|
+
let anyBaseDeployment = false
|
|
290
|
+
for (const abs of baseFiles) {
|
|
291
|
+
const rel = relative(root, abs).replaceAll('\\', '/') || abs
|
|
292
|
+
let raw
|
|
293
|
+
try {
|
|
294
|
+
raw = await readFile(abs, 'utf8')
|
|
295
|
+
} catch (error) {
|
|
296
|
+
const msg = error instanceof Error ? error.message : String(error)
|
|
297
|
+
fail(`${rel}: не вдалося прочитати (${msg})`)
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
const body = stripBom(raw)
|
|
301
|
+
const lines = body.split(/\r?\n/u)
|
|
302
|
+
const first = lines[0] ?? ''
|
|
303
|
+
const rest = MODELINE_RE.test(first.trim()) ? lines.slice(1).join('\n') : body
|
|
304
|
+
/** @type {import('yaml').Document[]} */
|
|
305
|
+
let docs
|
|
306
|
+
try {
|
|
307
|
+
docs = parseAllDocuments(rest)
|
|
308
|
+
} catch (error) {
|
|
309
|
+
const msg = error instanceof Error ? error.message : String(error)
|
|
310
|
+
fail(`${rel}: YAML (${msg})`)
|
|
311
|
+
return
|
|
312
|
+
}
|
|
313
|
+
for (const doc of docs) {
|
|
314
|
+
if (doc.errors.length > 0) {
|
|
315
|
+
continue
|
|
316
|
+
}
|
|
317
|
+
const obj = doc.toJSON()
|
|
318
|
+
if (!isDeploymentDoc(obj)) {
|
|
319
|
+
continue
|
|
320
|
+
}
|
|
321
|
+
anyBaseDeployment = true
|
|
322
|
+
if (!deploymentDocumentHasAbieBasePreemNodeSelector(obj)) {
|
|
323
|
+
fail(`${rel}: Deployment у base: потрібен spec.template.spec.nodeSelector.preem: true (або 'true') — abie.mdc`)
|
|
324
|
+
return
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (anyBaseDeployment) {
|
|
329
|
+
pass('Deployment у …/base/…: nodeSelector.preem відповідає abie.mdc')
|
|
330
|
+
} else {
|
|
331
|
+
pass('Немає Deployment у шляхах …/base/… — перевірку preem у base пропущено')
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
192
335
|
/**
|
|
193
336
|
* Прибирає BOM на початку файлу.
|
|
194
337
|
* @param {string} s вміст
|
|
@@ -198,6 +341,261 @@ function stripBom(s) {
|
|
|
198
341
|
return s.startsWith('\uFEFF') ? s.slice(1) : s
|
|
199
342
|
}
|
|
200
343
|
|
|
344
|
+
/**
|
|
345
|
+
* Чи рядок inline JSON6902 patch містить очікуваний **ua** nodeSelector (**op: add**, **preem: false**).
|
|
346
|
+
* @param {string} patchText поле **patch** у kustomization
|
|
347
|
+
* @returns {boolean} true, якщо критерії abie.mdc виконано
|
|
348
|
+
*/
|
|
349
|
+
function jsonPatchTextHasUaDeploymentNodeSelector(patchText) {
|
|
350
|
+
if (typeof patchText !== 'string' || patchText.trim() === '') {
|
|
351
|
+
return false
|
|
352
|
+
}
|
|
353
|
+
if (!/op:\s*add\b/u.test(patchText)) {
|
|
354
|
+
return false
|
|
355
|
+
}
|
|
356
|
+
if (!/path:\s*\/spec\/template\/spec\/nodeSelector\b/u.test(patchText)) {
|
|
357
|
+
return false
|
|
358
|
+
}
|
|
359
|
+
if (!/\bpreem:\s*['"]?false['"]?\b/u.test(patchText)) {
|
|
360
|
+
return false
|
|
361
|
+
}
|
|
362
|
+
return true
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Чи рядок inline JSON6902 patch містить очікуваний **ru** nodeSelector (**op: replace**, **yandex.cloud/preemptible: false**).
|
|
367
|
+
* @param {string} patchText поле **patch** у kustomization
|
|
368
|
+
* @returns {boolean} true, якщо критерії abie.mdc виконано
|
|
369
|
+
*/
|
|
370
|
+
function jsonPatchTextHasRuDeploymentNodeSelector(patchText) {
|
|
371
|
+
if (typeof patchText !== 'string' || patchText.trim() === '') {
|
|
372
|
+
return false
|
|
373
|
+
}
|
|
374
|
+
if (!/op:\s*replace\b/u.test(patchText)) {
|
|
375
|
+
return false
|
|
376
|
+
}
|
|
377
|
+
if (!/path:\s*\/spec\/template\/spec\/nodeSelector\b/u.test(patchText)) {
|
|
378
|
+
return false
|
|
379
|
+
}
|
|
380
|
+
if (!/yandex\.cloud\/preemptible:\s*['"]?false['"]?/u.test(patchText)) {
|
|
381
|
+
return false
|
|
382
|
+
}
|
|
383
|
+
return true
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Чи один елемент **patches** у kustomization відповідає abie nodeSelector для заданого **mode**.
|
|
388
|
+
* @param {unknown} p елемент масиву **patches**
|
|
389
|
+
* @param {'ua' | 'ru'} mode який overlay перевіряти
|
|
390
|
+
* @returns {boolean} true, якщо patch відповідає abie для **mode**
|
|
391
|
+
*/
|
|
392
|
+
function inlineKustomizationPatchMatchesAbieMode(p, mode) {
|
|
393
|
+
if (p === null || typeof p !== 'object' || Array.isArray(p)) {
|
|
394
|
+
return false
|
|
395
|
+
}
|
|
396
|
+
const pr = /** @type {Record<string, unknown>} */ (p)
|
|
397
|
+
const target = pr.target
|
|
398
|
+
if (target === null || typeof target !== 'object' || Array.isArray(target)) {
|
|
399
|
+
return false
|
|
400
|
+
}
|
|
401
|
+
const tg = /** @type {Record<string, unknown>} */ (target)
|
|
402
|
+
if (tg.kind !== 'Deployment') {
|
|
403
|
+
return false
|
|
404
|
+
}
|
|
405
|
+
const patchStr = pr.patch
|
|
406
|
+
if (typeof patchStr !== 'string') {
|
|
407
|
+
return false
|
|
408
|
+
}
|
|
409
|
+
if (mode === 'ua' && jsonPatchTextHasUaDeploymentNodeSelector(patchStr)) {
|
|
410
|
+
return true
|
|
411
|
+
}
|
|
412
|
+
if (mode === 'ru' && jsonPatchTextHasRuDeploymentNodeSelector(patchStr)) {
|
|
413
|
+
return true
|
|
414
|
+
}
|
|
415
|
+
return false
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Чи один YAML-документ kustomization містить відповідний inline patch на Deployment.
|
|
420
|
+
* @param {import('yaml').Document} doc документ після **parseAllDocuments**
|
|
421
|
+
* @param {'ua' | 'ru'} mode який overlay перевіряти
|
|
422
|
+
* @returns {boolean} true, якщо знайдено відповідний patch
|
|
423
|
+
*/
|
|
424
|
+
function kustomizationDocumentHasAbieDeploymentNodeSelectorPatch(doc, mode) {
|
|
425
|
+
if (doc.errors.length > 0) {
|
|
426
|
+
return false
|
|
427
|
+
}
|
|
428
|
+
const root = doc.toJSON()
|
|
429
|
+
if (root === null || typeof root !== 'object' || Array.isArray(root)) {
|
|
430
|
+
return false
|
|
431
|
+
}
|
|
432
|
+
const rec = /** @type {Record<string, unknown>} */ (root)
|
|
433
|
+
if (rec.kind !== 'Kustomization') {
|
|
434
|
+
return false
|
|
435
|
+
}
|
|
436
|
+
const patches = rec.patches
|
|
437
|
+
if (!Array.isArray(patches)) {
|
|
438
|
+
return false
|
|
439
|
+
}
|
|
440
|
+
for (const p of patches) {
|
|
441
|
+
if (inlineKustomizationPatchMatchesAbieMode(p, mode)) {
|
|
442
|
+
return true
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return false
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Чи **kustomization.yaml** містить inline **patches** на **Deployment** з nodeSelector за abie.mdc (**ua** або **ru**).
|
|
450
|
+
* @param {string} raw повний текст файлу
|
|
451
|
+
* @param {'ua' | 'ru'} mode який overlay перевіряти
|
|
452
|
+
* @returns {boolean} true, якщо знайдено відповідний patch
|
|
453
|
+
*/
|
|
454
|
+
export function kustomizationHasAbieDeploymentNodeSelectorPatch(raw, mode) {
|
|
455
|
+
const body = stripBom(raw)
|
|
456
|
+
const lines = body.split(/\r?\n/u)
|
|
457
|
+
const first = lines[0] ?? ''
|
|
458
|
+
const rest = MODELINE_RE.test(first.trim()) ? lines.slice(1).join('\n') : body
|
|
459
|
+
/** @type {import('yaml').Document[]} */
|
|
460
|
+
let docs
|
|
461
|
+
try {
|
|
462
|
+
docs = parseAllDocuments(rest)
|
|
463
|
+
} catch {
|
|
464
|
+
return false
|
|
465
|
+
}
|
|
466
|
+
for (const doc of docs) {
|
|
467
|
+
if (kustomizationDocumentHasAbieDeploymentNodeSelectorPatch(doc, mode)) {
|
|
468
|
+
return true
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return false
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** Домени **hostnames** для overlay **ua** (підрядки у JSON6902-тексті patch), abie.mdc. */
|
|
475
|
+
const ABIE_UA_HTTPROUTE_HOST_MARKERS = ['abie.app', 'vybeerai.com.ua', '*.abie.app', '*.vybeerai.com.ua']
|
|
476
|
+
|
|
477
|
+
/** Домени **hostnames** для overlay **ru** (підрядки), abie.mdc. */
|
|
478
|
+
const ABIE_RU_HTTPROUTE_HOST_MARKERS = [
|
|
479
|
+
'napitkivmeste.tech',
|
|
480
|
+
'выбирайонлайн.рф',
|
|
481
|
+
'*.napitkivmeste.tech',
|
|
482
|
+
'*.выбирайонлайн.рф'
|
|
483
|
+
]
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Збирає тексти inline **patch** для **HTTPRoute/nginx-run** з одного розібраного документа **Kustomization**.
|
|
487
|
+
* @param {import('yaml').Document} doc документ після **parseAllDocuments**
|
|
488
|
+
* @returns {string[]} непорожні рядки **patch**
|
|
489
|
+
*/
|
|
490
|
+
function collectNginxRunPatchStringsFromKustomizationDoc(doc) {
|
|
491
|
+
if (doc.errors.length > 0) {
|
|
492
|
+
return []
|
|
493
|
+
}
|
|
494
|
+
const root = doc.toJSON()
|
|
495
|
+
if (root === null || typeof root !== 'object' || Array.isArray(root)) {
|
|
496
|
+
return []
|
|
497
|
+
}
|
|
498
|
+
const rec = /** @type {Record<string, unknown>} */ (root)
|
|
499
|
+
if (rec.kind !== 'Kustomization') {
|
|
500
|
+
return []
|
|
501
|
+
}
|
|
502
|
+
const patches = rec.patches
|
|
503
|
+
if (!Array.isArray(patches)) {
|
|
504
|
+
return []
|
|
505
|
+
}
|
|
506
|
+
/** @type {string[]} */
|
|
507
|
+
const out = []
|
|
508
|
+
for (const p of patches) {
|
|
509
|
+
if (p === null || typeof p !== 'object' || Array.isArray(p)) {
|
|
510
|
+
continue
|
|
511
|
+
}
|
|
512
|
+
const pr = /** @type {Record<string, unknown>} */ (p)
|
|
513
|
+
const target = pr.target
|
|
514
|
+
if (target === null || typeof target !== 'object' || Array.isArray(target)) {
|
|
515
|
+
continue
|
|
516
|
+
}
|
|
517
|
+
const tg = /** @type {Record<string, unknown>} */ (target)
|
|
518
|
+
if (tg.kind !== 'HTTPRoute' || tg.name !== 'nginx-run') {
|
|
519
|
+
continue
|
|
520
|
+
}
|
|
521
|
+
const patchStr = pr.patch
|
|
522
|
+
if (typeof patchStr === 'string' && patchStr.trim() !== '') {
|
|
523
|
+
out.push(patchStr)
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return out
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Об’єднує всі inline **JSON6902**-фрагменти для **HTTPRoute/nginx-run** у **kustomization.yaml** (усі документи у файлі).
|
|
531
|
+
* @param {string} raw повний текст файлу
|
|
532
|
+
* @returns {string} текст для **`validateAbieNginxRunHttpRoutePatches`** (може бути порожнім)
|
|
533
|
+
*/
|
|
534
|
+
export function getCombinedNginxRunPatchTextFromKustomization(raw) {
|
|
535
|
+
const body = stripBom(raw)
|
|
536
|
+
const lines = body.split(/\r?\n/u)
|
|
537
|
+
const first = lines[0] ?? ''
|
|
538
|
+
const rest = MODELINE_RE.test(first.trim()) ? lines.slice(1).join('\n') : body
|
|
539
|
+
/** @type {import('yaml').Document[]} */
|
|
540
|
+
let docs
|
|
541
|
+
try {
|
|
542
|
+
docs = parseAllDocuments(rest)
|
|
543
|
+
} catch {
|
|
544
|
+
return ''
|
|
545
|
+
}
|
|
546
|
+
/** @type {string[]} */
|
|
547
|
+
const chunks = []
|
|
548
|
+
for (const doc of docs) {
|
|
549
|
+
chunks.push(...collectNginxRunPatchStringsFromKustomizationDoc(doc))
|
|
550
|
+
}
|
|
551
|
+
return chunks.join('\n')
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Перевіряє об’єднаний текст patch(ів) **HTTPRoute/nginx-run** на відповідність abie.mdc.
|
|
556
|
+
* @param {string} combined текст одного або кількох inline **patch** (з’єднаних перевідом рядка)
|
|
557
|
+
* @param {'ua' | 'ru'} mode **ua** або **ru**
|
|
558
|
+
* @returns {string | null} повідомлення про помилку або **null**
|
|
559
|
+
*/
|
|
560
|
+
export function validateAbieNginxRunHttpRoutePatches(combined, mode) {
|
|
561
|
+
if (typeof combined !== 'string' || combined.trim() === '') {
|
|
562
|
+
return `очікується patch target kind HTTPRoute name nginx-run (replace hostnames, parentRefs namespace ${mode}; для ru — також gwin… upgradeTypes websocket) — abie.mdc`
|
|
563
|
+
}
|
|
564
|
+
const hasHostnamesReplace = /-\s*op:\s*replace\b[\s\S]{0,200}?path:\s*\/spec\/hostnames\b/m.test(combined)
|
|
565
|
+
if (!hasHostnamesReplace) {
|
|
566
|
+
return 'HTTPRoute nginx-run: потрібен блок op replace з path /spec/hostnames (abie.mdc)'
|
|
567
|
+
}
|
|
568
|
+
const markers = mode === 'ua' ? ABIE_UA_HTTPROUTE_HOST_MARKERS : ABIE_RU_HTTPROUTE_HOST_MARKERS
|
|
569
|
+
if (!markers.some(m => combined.includes(m))) {
|
|
570
|
+
return `HTTPRoute nginx-run: у value для /spec/hostnames має бути один із доменів abie (${markers.join(', ')}) — abie.mdc`
|
|
571
|
+
}
|
|
572
|
+
const ns = mode === 'ua' ? 'ua' : 'ru'
|
|
573
|
+
const nsRe = new RegExp(
|
|
574
|
+
String.raw`path:\s*\/spec\/parentRefs\/0\/namespace\b[\s\S]{0,200}?value:\s*['"]?${ns}['"]?(?:\s|$)`,
|
|
575
|
+
'mu'
|
|
576
|
+
)
|
|
577
|
+
if (!nsRe.test(combined)) {
|
|
578
|
+
return `HTTPRoute nginx-run: потрібен replace path /spec/parentRefs/0/namespace з value ${ns} (abie.mdc)`
|
|
579
|
+
}
|
|
580
|
+
if (mode === 'ru') {
|
|
581
|
+
if (!/gwin\.yandex\.cloud\/rules\.http\.upgradeTypes:\s*['"]?websocket['"]?/m.test(combined)) {
|
|
582
|
+
return 'HTTPRoute nginx-run (ru): потрібна анотація gwin.yandex.cloud/rules.http.upgradeTypes: websocket (abie.mdc)'
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return null
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Чи **kustomization** містить валідні для abie патчі **HTTPRoute/nginx-run** (**ua** або **ru**).
|
|
590
|
+
* @param {string} raw повний текст **kustomization.yaml**
|
|
591
|
+
* @param {'ua' | 'ru'} mode overlay
|
|
592
|
+
* @returns {boolean} true, якщо **`validateAbieNginxRunHttpRoutePatches`** повертає **null**
|
|
593
|
+
*/
|
|
594
|
+
export function kustomizationHasAbieNginxRunHttpRoutePatch(raw, mode) {
|
|
595
|
+
const combined = getCombinedNginxRunPatchTextFromKustomization(raw)
|
|
596
|
+
return validateAbieNginxRunHttpRoutePatches(combined, mode) === null
|
|
597
|
+
}
|
|
598
|
+
|
|
201
599
|
/**
|
|
202
600
|
* Перевіряє **hc.yaml** на відповідність abie.mdc.
|
|
203
601
|
* @param {string} raw повний текст файлу
|
|
@@ -341,7 +739,7 @@ async function collectHealthCheckPolicyRelPaths(root, yamlAbs) {
|
|
|
341
739
|
}
|
|
342
740
|
|
|
343
741
|
/**
|
|
344
|
-
* Якщо є **HealthCheckPolicy**, вимагає **ru/kustomization.yaml** з patch видалення (
|
|
742
|
+
* Якщо є **HealthCheckPolicy**, вимагає **ru/kustomization.yaml** з patch видалення (**ruKustomizationHasHealthCheckDeletePatch** у **check-k8s**).
|
|
345
743
|
* @param {string} root корінь
|
|
346
744
|
* @param {string[]} yamlFilesAbs абсолютні шляхи yaml k8s
|
|
347
745
|
* @param {string[]} healthCheckPolicyRelativePaths відносні шляхи
|
|
@@ -377,6 +775,128 @@ async function ensureRuKustomizationHealthCheckDelete(root, yamlFilesAbs, health
|
|
|
377
775
|
)
|
|
378
776
|
}
|
|
379
777
|
|
|
778
|
+
/**
|
|
779
|
+
* Якщо є **Deployment** під **k8s**, вимагає в кожному overlay **ua** та **ru** (**kustomization.yaml**) JSON6902 patch nodeSelector (abie.mdc).
|
|
780
|
+
* @param {string} root корінь репозиторію
|
|
781
|
+
* @param {string[]} yamlFilesAbs yaml під k8s
|
|
782
|
+
* @param {(msg: string) => void} fail callback
|
|
783
|
+
* @returns {Promise<void>}
|
|
784
|
+
*/
|
|
785
|
+
async function ensureUaRuAbieNodeSelectorPatches(root, yamlFilesAbs, fail) {
|
|
786
|
+
const uaAbsList = yamlFilesAbs.filter(abs => isUaKustomizationPath(relative(root, abs).replaceAll('\\', '/') || abs))
|
|
787
|
+
if (uaAbsList.length === 0) {
|
|
788
|
+
fail(
|
|
789
|
+
'Є Deployment у k8s — додай ua/kustomization.yaml з inline patch на Deployment: op add, path /spec/template/spec/nodeSelector, preem false (abie.mdc)'
|
|
790
|
+
)
|
|
791
|
+
return
|
|
792
|
+
}
|
|
793
|
+
for (const abs of uaAbsList) {
|
|
794
|
+
const rel = relative(root, abs).replaceAll('\\', '/') || abs
|
|
795
|
+
let raw
|
|
796
|
+
try {
|
|
797
|
+
raw = await readFile(abs, 'utf8')
|
|
798
|
+
} catch (error) {
|
|
799
|
+
const msg = error instanceof Error ? error.message : String(error)
|
|
800
|
+
fail(`${rel}: не вдалося прочитати (${msg})`)
|
|
801
|
+
return
|
|
802
|
+
}
|
|
803
|
+
if (!kustomizationHasAbieDeploymentNodeSelectorPatch(raw, 'ua')) {
|
|
804
|
+
fail(
|
|
805
|
+
`${rel}: потрібен patch target kind Deployment з op: add, path /spec/template/spec/nodeSelector та preem: false (abie.mdc)`
|
|
806
|
+
)
|
|
807
|
+
return
|
|
808
|
+
}
|
|
809
|
+
pass(`${rel}: nodeSelector patch (ua) відповідає abie.mdc`)
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const ruAbsList = yamlFilesAbs.filter(abs => isRuKustomizationPath(relative(root, abs).replaceAll('\\', '/') || abs))
|
|
813
|
+
if (ruAbsList.length === 0) {
|
|
814
|
+
fail(
|
|
815
|
+
'Є Deployment у k8s — додай ru/kustomization.yaml з inline patch на Deployment: op replace, path /spec/template/spec/nodeSelector, yandex.cloud/preemptible false (abie.mdc)'
|
|
816
|
+
)
|
|
817
|
+
return
|
|
818
|
+
}
|
|
819
|
+
for (const abs of ruAbsList) {
|
|
820
|
+
const rel = relative(root, abs).replaceAll('\\', '/') || abs
|
|
821
|
+
let raw
|
|
822
|
+
try {
|
|
823
|
+
raw = await readFile(abs, 'utf8')
|
|
824
|
+
} catch (error) {
|
|
825
|
+
const msg = error instanceof Error ? error.message : String(error)
|
|
826
|
+
fail(`${rel}: не вдалося прочитати (${msg})`)
|
|
827
|
+
return
|
|
828
|
+
}
|
|
829
|
+
if (!kustomizationHasAbieDeploymentNodeSelectorPatch(raw, 'ru')) {
|
|
830
|
+
fail(
|
|
831
|
+
`${rel}: потрібен patch target kind Deployment з op: replace, path /spec/template/spec/nodeSelector та yandex.cloud/preemptible: false (abie.mdc)`
|
|
832
|
+
)
|
|
833
|
+
return
|
|
834
|
+
}
|
|
835
|
+
pass(`${rel}: nodeSelector patch (ru) відповідає abie.mdc`)
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* Якщо є **Deployment** під **k8s**, вимагає в кожному overlay **ua** та **ru** patch **HTTPRoute/nginx-run** (abie.mdc).
|
|
841
|
+
* @param {string} root корінь репозиторію
|
|
842
|
+
* @param {string[]} yamlFilesAbs yaml під k8s
|
|
843
|
+
* @param {(msg: string) => void} fail callback
|
|
844
|
+
* @returns {Promise<void>}
|
|
845
|
+
*/
|
|
846
|
+
async function ensureUaRuAbieHttpRoutePatches(root, yamlFilesAbs, fail) {
|
|
847
|
+
const uaAbsList = yamlFilesAbs.filter(abs => isUaKustomizationPath(relative(root, abs).replaceAll('\\', '/') || abs))
|
|
848
|
+
if (uaAbsList.length === 0) {
|
|
849
|
+
fail(
|
|
850
|
+
'Є Deployment у k8s — додай ua/kustomization.yaml з patch HTTPRoute nginx-run (hostnames, parentRefs namespace ua) — abie.mdc'
|
|
851
|
+
)
|
|
852
|
+
return
|
|
853
|
+
}
|
|
854
|
+
for (const abs of uaAbsList) {
|
|
855
|
+
const rel = relative(root, abs).replaceAll('\\', '/') || abs
|
|
856
|
+
let raw
|
|
857
|
+
try {
|
|
858
|
+
raw = await readFile(abs, 'utf8')
|
|
859
|
+
} catch (error) {
|
|
860
|
+
const msg = error instanceof Error ? error.message : String(error)
|
|
861
|
+
fail(`${rel}: не вдалося прочитати (${msg})`)
|
|
862
|
+
return
|
|
863
|
+
}
|
|
864
|
+
const combined = getCombinedNginxRunPatchTextFromKustomization(raw)
|
|
865
|
+
const v = validateAbieNginxRunHttpRoutePatches(combined, 'ua')
|
|
866
|
+
if (v !== null) {
|
|
867
|
+
fail(`${rel}: ${v}`)
|
|
868
|
+
return
|
|
869
|
+
}
|
|
870
|
+
pass(`${rel}: HTTPRoute nginx-run (ua) відповідає abie.mdc`)
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
const ruAbsList = yamlFilesAbs.filter(abs => isRuKustomizationPath(relative(root, abs).replaceAll('\\', '/') || abs))
|
|
874
|
+
if (ruAbsList.length === 0) {
|
|
875
|
+
fail(
|
|
876
|
+
'Є Deployment у k8s — додай ru/kustomization.yaml з patch HTTPRoute nginx-run (hostnames, namespace ru, gwin websocket) — abie.mdc'
|
|
877
|
+
)
|
|
878
|
+
return
|
|
879
|
+
}
|
|
880
|
+
for (const abs of ruAbsList) {
|
|
881
|
+
const rel = relative(root, abs).replaceAll('\\', '/') || abs
|
|
882
|
+
let raw
|
|
883
|
+
try {
|
|
884
|
+
raw = await readFile(abs, 'utf8')
|
|
885
|
+
} catch (error) {
|
|
886
|
+
const msg = error instanceof Error ? error.message : String(error)
|
|
887
|
+
fail(`${rel}: не вдалося прочитати (${msg})`)
|
|
888
|
+
return
|
|
889
|
+
}
|
|
890
|
+
const combined = getCombinedNginxRunPatchTextFromKustomization(raw)
|
|
891
|
+
const v = validateAbieNginxRunHttpRoutePatches(combined, 'ru')
|
|
892
|
+
if (v !== null) {
|
|
893
|
+
fail(`${rel}: ${v}`)
|
|
894
|
+
return
|
|
895
|
+
}
|
|
896
|
+
pass(`${rel}: HTTPRoute nginx-run (ru) відповідає abie.mdc`)
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
380
900
|
/**
|
|
381
901
|
* Перевіряє відповідність проєкту правилам abie.mdc.
|
|
382
902
|
* @returns {Promise<number>} 0 — OK, 1 — є порушення
|
|
@@ -457,6 +977,8 @@ export async function check() {
|
|
|
457
977
|
)
|
|
458
978
|
}
|
|
459
979
|
}
|
|
980
|
+
pass('Є Deployment — перевіряємо base: spec.template.spec.nodeSelector.preem (abie.mdc)')
|
|
981
|
+
await ensureAbieBaseDeploymentPreemNodeSelector(root, yamlFiles, fail)
|
|
460
982
|
} else {
|
|
461
983
|
pass('Немає Deployment у дереві k8s — перевірку hc.yaml пропущено')
|
|
462
984
|
}
|
|
@@ -464,5 +986,12 @@ export async function check() {
|
|
|
464
986
|
const healthCheckPolicyRelativePaths = await collectHealthCheckPolicyRelPaths(root, yamlFiles)
|
|
465
987
|
await ensureRuKustomizationHealthCheckDelete(root, yamlFiles, healthCheckPolicyRelativePaths, fail)
|
|
466
988
|
|
|
989
|
+
if (deploymentDirs.size > 0) {
|
|
990
|
+
pass('Є Deployment — перевіряємо nodeSelector у ua/ru kustomization (abie.mdc)')
|
|
991
|
+
await ensureUaRuAbieNodeSelectorPatches(root, yamlFiles, fail)
|
|
992
|
+
pass('Є Deployment — перевіряємо HTTPRoute nginx-run у ua/ru kustomization (abie.mdc)')
|
|
993
|
+
await ensureUaRuAbieHttpRoutePatches(root, yamlFiles, fail)
|
|
994
|
+
}
|
|
995
|
+
|
|
467
996
|
return exitCode
|
|
468
997
|
}
|
package/scripts/check-k8s.mjs
CHANGED
|
@@ -666,16 +666,6 @@ function extractApiVersionAndKind(doc) {
|
|
|
666
666
|
}
|
|
667
667
|
}
|
|
668
668
|
|
|
669
|
-
/**
|
|
670
|
-
* Чи відносний шлях вказує на **`ru/kustomization.yaml`** (сегмент **`ru`** перед ім’ям файлу).
|
|
671
|
-
* @param {string} rel шлях від кореня репозиторію
|
|
672
|
-
* @returns {boolean} true, якщо це `…/ru/kustomization.yaml`
|
|
673
|
-
*/
|
|
674
|
-
export function isRuKustomizationPath(rel) {
|
|
675
|
-
const norm = rel.replaceAll('\\', '/')
|
|
676
|
-
return /(^|\/)ru\/kustomization\.yaml$/u.test(norm)
|
|
677
|
-
}
|
|
678
|
-
|
|
679
669
|
/**
|
|
680
670
|
* Чи вміст overlay **`ru/kustomization.yaml`** містить Kustomize patch видалення **HealthCheckPolicy**.
|
|
681
671
|
* @param {string} raw повний текст файлу
|