@nitra/cursor 1.8.19 → 1.8.22

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/README.md CHANGED
@@ -27,9 +27,25 @@
27
27
  | `js-format` | Правила форматування JavaScript ecosystem (oxfmt) |
28
28
  | `npm-module` | Структура репозиторію для npm-модуля (bun mono) |
29
29
  | `text` | Текстові файли: cspell, CI |
30
+ | `k8s` | Kubernetes YAML, Kustomize, kubeconform |
30
31
 
31
32
  Щоб використовувати конкретну версію правил, оновіть залежність `@nitra/cursor` у проєкті (`bun add -d @nitra/cursor@<версія>` тощо). Поле `version` у `.n-cursor.json`, якщо воно лишилось у старих конфігах, **ігнорується**.
32
33
 
34
+ ### Правило `k8s` і Kustomize
35
+
36
+ У цільовому репозиторії з маніфестами під **`**/k8s`** дотримуйтесь **`mdc/k8s.mdc`** з пакету (після синку — `.cursor/rules/n-k8s.mdc`або копія з`node_modules/@nitra/cursor/mdc/k8s.mdc`).
37
+
38
+ Коротко:
39
+
40
+ - **Структура Kustomize:** спільне виноситься в **`base`**; вміст **base** відповідає тому, як має виглядати середовище **dev**; окремої директорії **`dev/`** немає — за dev відповідає **`base`**. У інших середовищах — тонкі **overlays** (часто лише **`kustomization.yaml`** і patches / оверрайди).
41
+ - У каталозі **`k8s`** має бути **`README.md`** з описом дерев і застосування.
42
+ - **Namespace** задається в **`kustomization.yaml`** (`namespace:`), а не через **`metadata.namespace`** у кожному ресурсі; окремі patches лише на зміну **namespace** не потрібні.
43
+ - У **Deployment** для кожного контейнера: **`resources`**, **`imagePullPolicy: Always`** (перевіряє **`npx @nitra/cursor check k8s`**).
44
+ - Рядки в **base**, які змінюються в overlays, позначайте коментарем на рядку (узгоджено в команді), наприклад: `# буде замінено через kustomize`.
45
+ - Після перенесення в **`base`** / overlays **видаляйте** застарілі маніфести та каталоги, які більше не потрібні.
46
+
47
+ Повний текст правил — у **`k8s.mdc`**; programmatic перевірки — у **`npm/scripts/check-k8s.mjs`** (у встановленому пакеті — `scripts/check-k8s.mjs`).
48
+
33
49
  ### v8r і власний каталог схем
34
50
 
35
51
  Скрипт `scripts/run-v8r.mjs` передає в v8r каталог **`schemas/v8r-catalog.json`** пакета автоматично (у репозиторії той самий файл, що й `npm/schemas/v8r-catalog.json` від кореня монорепо). Якщо викликаєш `bunx v8r` напряму, передай `-c`: локально `node_modules/@nitra/cursor/schemas/v8r-catalog.json` або [unpkg](https://unpkg.com/@nitra/cursor/schemas/v8r-catalog.json). JSON Schema конфігурації: [n-cursor.json](https://unpkg.com/@nitra/cursor/schemas/n-cursor.json).
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.13'
3
+ version: '1.16'
4
4
  globs: "**/k8s/**/*.{yaml,yml}"
5
5
  alwaysApply: false
6
6
  ---
@@ -125,9 +125,81 @@ resources: {}
125
125
 
126
126
  Так маніфест явно резервує місце під **`requests` / `limits`** і уникає випадкового пропуску секції. **`check k8s`** перевіряє це для кожного YAML-документа **`Deployment`** у файлах під **`k8s`**.
127
127
 
128
+ У кожному контейнері **`Deployment`** має бути **`imagePullPolicy: Always`** (див. **`check k8s`**).
129
+
130
+ ## Kustomize: структура каталогів (`base` / overlays)
131
+
132
+ Трансформуй дерева **`**/k8s`**, щоб **винести спільне** через [Kustomize](https://kustomize.io/): один канонічний **`base`** і тонкі **overlays** для інших середовищ.
133
+
134
+ ### Джерело правди — середовище dev
135
+
136
+ - За основу бери **все, що відповідає середовищу dev** (як воно має виглядати в кластері для dev).
137
+ - У **такому вигляді** цей набір стає каталогом **`base`**: спільні маніфести без окремої директорії **`dev/`**.
138
+ - Окремої директорії **`dev`** **не повинно існувати**: за середовище **dev** відповідає **`base`** (застосування **`kubectl apply -k …/base`** або збірка з overlay, який посилається на `base`).
139
+
140
+ ### Overlays (не-dev)
141
+
142
+ - У каталозі кожного іншого середовища (наприклад **`ru/`**, **`prod/`**) має бути **мінімум файлів**: типово лише **`kustomization.yaml`** (посилання на `base`, `patches`, `replacements`, `components` тощо) і ресурси чи додаткові YAML, **необхідні лише для цього overlay**.
143
+ - Відмінності від dev вносяться **оверрайдами** (patches, `images`, `replicas`, `configMapGenerator` тощо), а не копіюванням повного дерева з `base`.
144
+
145
+ ### Namespace
146
+
147
+ - У **`base/kustomization.yaml`** задай **`namespace: dev`** (або узгоджене ім’я для dev **namespace**), щоб **усі ресурси base** потрапляли в цей namespace через Kustomize.
148
+ - У **маніфестах ресурсів** (Deployment, Service, …) **не вказуй** **`metadata.namespace`** — **namespace** задається **лише в файлах Kustomize** (`kustomization.yaml` / `kustomization.yml`), полем верхнього рівня **`namespace:`**.
149
+ - **Не додавай** окремі **patches** Kustomize, які лише змінюють **namespace**: **namespace** визначає Kustomize; у overlays додаткові зміни — без дублювання логіки **namespace**.
150
+
151
+ ### Рядки, що змінюються між середовищами
152
+
153
+ - У маніфестах у **`base`** для полів (або значень), які **будуть відрізнятися** в інших середовищах, на **тому самому рядку** додай коментар:
154
+
155
+ ```yaml
156
+ image: my-app:dev-tag # буде замінено через kustomize
157
+ ```
158
+
159
+ Текст коментаря узгодь у команді; важливо, щоб було видно, що значення **навіть у base** може бути замінене overlay.
160
+
161
+ ### Документація в дереві k8s
162
+
163
+ - У каталозі **`k8s`** (корінь оголошеного дерева маніфестів) має бути **`README.md`**: коротко опиши структуру (`base`, overlays), як зібрати/застосувати середовища, посилання на внутрішні правила команди.
164
+
165
+ ### Міграція зі старої структури
166
+
167
+ - Після перенесення маніфестів у **`base`** та overlays і перевірки (**`check k8s`**, **`lint-k8s`**) **видали** застарілі файли та директорії, які замінені новою схемою (дубльовані копії, колишні шляхи без Kustomize), щоб у репозиторії не залишалося зайвих або суперечливих маніфестів.
168
+
169
+ ## Ingress → Gateway API (GKE)
170
+
171
+ Якщо в дереві **`k8s`** трапляється маніфест з **`kind: Ingress`**, його потрібно **замінити на Gateway API**, а не залишати Ingress.
172
+
173
+ 1. **HTTPRoute** — окремий файл **`hr.yaml`** (або узгоджене ім’я в команді), **`kind: HTTPRoute`**, `apiVersion` з групи **`gateway.networking.k8s.io`** (див. приклад `$schema` для HTTPRoute у розділі «Визначення схеми YAML»).
174
+ 2. **HealthCheckPolicy (GKE)** — окремий файл **`hc.yaml`**, наприклад:
175
+
176
+ ```yaml
177
+ apiVersion: networking.gke.io/v1
178
+ kind: HealthCheckPolicy
179
+ ```
180
+
181
+ Для `$schema` у першому рядку див. приклад **HealthCheckPolicy** у тому ж розділі (datree CRDs-catalog).
182
+
183
+ 3. **Overlay `ru`:** у **`ru/kustomization.yaml`** (шлях з сегментами **`…/ru/kustomization.yaml`** під **`k8s`**) додай **видалення** ресурсу HealthCheckPolicy для середовища, де політика не потрібна (підстав **реальне ім’я** замість `SERVICE_NAME`):
184
+
185
+ ```yaml
186
+ patches:
187
+ - target:
188
+ kind: HealthCheckPolicy
189
+ patch: |-
190
+ kind: HealthCheckPolicy
191
+ metadata:
192
+ name: SERVICE_NAME
193
+ $patch: delete
194
+ ```
195
+
196
+ За потреби розшир **`target`** (`name`, `namespace`), щоб однозначно вказати об’єкт.
197
+
198
+ **`check k8s`:** заборонено **`kind: Ingress`**; якщо в проєкті є **`kind: HealthCheckPolicy`**, має існувати хоча б один **`ru/kustomization.yaml`** під **`k8s`** із блоком видалення (**`$patch: delete`** для **HealthCheckPolicy**).
199
+
128
200
  ## Перевірка
129
201
 
130
- **`npx @nitra/cursor check k8s`** — узгодженість першого рядка (`$schema`), один рядок `# yaml-language-server` на файл, правило для `.yml`, відповідність URL першому YAML-документу (до `---`) за логікою нижче; для кожного документа **`Deployment`** у файлінаявність **`containers[].resources`** (див. розділ **Deployment: `resources`**). Якщо під `k8s` немає yaml/yml — перевірку пропущено. Інший зміст маніфесту — вручну / **`lint-k8s`**.
202
+ **`npx @nitra/cursor check k8s`** — узгодженість першого рядка (`$schema`), один рядок `# yaml-language-server` на файл, правило для `.yml`, відповідність URL першому YAML-документу (до `---`) за логікою нижче; для кожного документа **`Deployment`** — **`containers[].resources`**, **`imagePullPolicy: Always`**; у файлах **не** `kustomization.yaml` / `kustomization.yml` заборона **`metadata.namespace`** (**namespace** лише в Kustomize, див. **Kustomize: структура каталогів**); заборона **`kind: Ingress`**; наявність **`HealthCheckPolicy`** — вимога до **`ru/kustomization.yaml`** з **`$patch: delete`** (див. **Ingress → Gateway API**). Якщо під `k8s` немає yaml/yml — перевірку пропущено. Інший зміст маніфесту — вручну / **`lint-k8s`**.
131
203
 
132
204
  Після змін у маніфестах: **`bun run lint-k8s`** (kubeconform + kubescape; обов'язково для проєктів з правилом **`k8s`** у **`.n-cursor.json`**).
133
205
 
@@ -137,13 +209,18 @@ resources: {}
137
209
 
138
210
  - Обхід з пропуском `node_modules`, `.git`, `dist`, `coverage`, `.turbo`, `.next`.
139
211
  - **`file:`** у `$schema` — URL до apiVersion/kind не звіряється; **`https:`** — kustomization за іменем файлу → Schema Store; далі перевіряється **`EXPLICIT_K8S_SCHEMAS`** (`Map`: `apiVersion` + `kind` + `type`, для записів без `type` у маніфесті — третій компонент **`*`**); потім `v1` → yannh; група з `YANNH_GROUPS` → yannh; інакше → datree (GitHub Pages), крім рядків явної таблиці (наприклад **InfisicalSecret** — raw на `main`).
140
- - У кожному YAML-документі з **`kind: Deployment`** — парсинг через **`yaml`**: у кожного елемента **`spec.template.spec.containers[]`** має існувати ключ **`resources`** зі значенням-об'єктом (допускається порожній **`{}`**).
212
+ - У кожному YAML-документі з **`kind: Deployment`** — парсинг через **`yaml`**: у кожного елемента **`spec.template.spec.containers[]`** має існувати ключ **`resources`** зі значенням-об'єктом (допускається порожній **`{}`**); у кожного контейнера — **`imagePullPolicy: Always`**.
213
+ - У файлах, ім’я яких **не** `kustomization.yaml` / `kustomization.yml`, у кожному документі — заборона поля **`metadata.namespace`** (**namespace** задається в Kustomize).
214
+ - Документи з **`kind: Ingress`** — заборонені (потрібен перехід на Gateway API, див. розділ **Ingress → Gateway API**).
215
+ - Якщо в будь-якому файлі під **`k8s`** є **`kind: HealthCheckPolicy`**, серед файлів має бути **`ru/kustomization.yaml`** (сегмент шляху **`ru`** перед іменем файлу), а його вміст — patch видалення **HealthCheckPolicy** з **`$patch: delete`** (див. той самий розділ).
141
216
 
142
217
  ## Коли застосовувати (агентам)
143
218
 
144
219
  - Зміни в k8s YAML — після правок **`check k8s`**.
145
220
  - Якщо перший рядок уже коректний і URL відповідає `apiVersion` / `kind` — не дублюй; змінився ресурс — онови лише `$schema`.
146
- - У **`Deployment`** без поля **`resources`** у контейнері — додай **`resources: {}`** (див. розділ **Deployment: `resources`**).
221
+ - У **`Deployment`** без поля **`resources`** у контейнері — додай **`resources: {}`** (див. розділ **Deployment: `resources`**); додай **`imagePullPolicy: Always`** для кожного контейнера.
222
+ - Дотримуйся структури **Kustomize** (`base` = dev, overlays без дублювання `base`, **`README.md`** у `k8s`, коментарі для рядків, що змінюються в overlay).
223
+ - Після міграції на нову структуру **видали** застарілі файли та каталоги, які вже замінені (див. **Міграція зі старої структури**).
147
224
 
148
225
  ## Визначення схеми YAML (канон)
149
226
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.8.19",
3
+ "version": "1.8.22",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -7,7 +7,13 @@
7
7
  *
8
8
  * Додатково: у кожному YAML-документі з **`kind: Deployment`** у кожного контейнера
9
9
  * **`spec.template.spec.containers[]`** має бути ключ **`resources`** (значення — об'єкт, допускається
10
- * порожній **`{}`**).
10
+ * порожній **`{}`**) та **`imagePullPolicy: Always`**.
11
+ *
12
+ * У файлах **не** `kustomization.yaml` / `kustomization.yml` у документах не має бути **`metadata.namespace`**
13
+ * (namespace лише в Kustomize).
14
+ *
15
+ * **`kind: Ingress`** заборонено (потрібен перехід на Gateway API). Якщо є **`HealthCheckPolicy`**,
16
+ * має існувати **`ru/kustomization.yaml`** з patch видалення цього kind (`$patch: delete`).
11
17
  *
12
18
  * Явні винятки до загальної логіки yannh/datree — таблиця **`EXPLICIT_K8S_SCHEMAS`** (`Map`): ключ
13
19
  * **`apiVersion`, `kind`, `type`** (для CRD без поля `type` у маніфесті — зірочка **`*`** як третій
@@ -205,6 +211,103 @@ function extractApiVersionAndKind(doc) {
205
211
  }
206
212
  }
207
213
 
214
+ /**
215
+ * Чи відносний шлях вказує на **`ru/kustomization.yaml`** (сегмент **`ru`** перед ім’ям файлу).
216
+ * @param {string} rel шлях від кореня репозиторію
217
+ * @returns {boolean} true, якщо це `…/ru/kustomization.yaml`
218
+ */
219
+ export function isRuKustomizationPath(rel) {
220
+ const norm = rel.replaceAll('\\', '/')
221
+ return /(^|\/)ru\/kustomization\.yaml$/u.test(norm)
222
+ }
223
+
224
+ /**
225
+ * Чи вміст overlay **`ru/kustomization.yaml`** містить Kustomize patch видалення **HealthCheckPolicy**.
226
+ * @param {string} raw повний текст файлу
227
+ * @returns {boolean} true, якщо є `$patch: delete` і блоки kind/metadata для HealthCheckPolicy
228
+ */
229
+ export function ruKustomizationHasHealthCheckDeletePatch(raw) {
230
+ if (!/\$patch:\s*delete/u.test(raw)) return false
231
+ if (!/kind:\s*HealthCheckPolicy/u.test(raw)) return false
232
+ if (!/metadata:/u.test(raw)) return false
233
+ if (!/name:\s*\S+/u.test(raw)) return false
234
+ return true
235
+ }
236
+
237
+ /**
238
+ * Шукає **Ingress** / **HealthCheckPolicy** у розібраних документах; реєструє порушення для Ingress.
239
+ * @param {string} rel відносний шлях до файлу
240
+ * @param {string} body YAML після modeline
241
+ * @param {(msg: string) => void} fail callback для помилки (Ingress)
242
+ * @param {string[]} healthCheckPolicyFiles накопичувач шляхів, де зустріли HealthCheckPolicy
243
+ * @returns {void}
244
+ */
245
+ function scanIngressAndHealthCheckPolicy(rel, body, fail, healthCheckPolicyFiles) {
246
+ /** @type {import('yaml').Document[]} */
247
+ let docs
248
+ try {
249
+ docs = parseAllDocuments(body)
250
+ } catch {
251
+ return
252
+ }
253
+
254
+ for (const [di, doc] of docs.entries()) {
255
+ if (doc.errors.length === 0) {
256
+ const obj = doc.toJSON()
257
+ if (obj !== null && typeof obj === 'object' && !Array.isArray(obj)) {
258
+ const rec = /** @type {Record<string, unknown>} */ (obj)
259
+ if (rec.kind === 'Ingress') {
260
+ fail(
261
+ `${rel}: знайдено kind: Ingress (документ ${di + 1}) — заміни на Gateway API: HTTPRoute (hr.yaml), HealthCheckPolicy (hc.yaml), patch у ru/kustomization.yaml (див. k8s.mdc)`
262
+ )
263
+ } else if (rec.kind === 'HealthCheckPolicy' && !healthCheckPolicyFiles.includes(rel)) {
264
+ healthCheckPolicyFiles.push(rel)
265
+ }
266
+ }
267
+ }
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Якщо у дереві k8s є HealthCheckPolicy, вимагає **ru/kustomization.yaml** з patch видалення.
273
+ * @param {string} root корінь cwd
274
+ * @param {string[]} yamlFiles абсолютні шляхи до yaml під k8s
275
+ * @param {string[]} healthCheckPolicyFiles відносні шляхи з HealthCheckPolicy
276
+ * @param {(msg: string) => void} fail callback для помилки (немає ru або немає patch)
277
+ * @returns {Promise<void>} завершення після перевірки overlay ru
278
+ */
279
+ async function ensureRuKustomizationHealthCheckDelete(root, yamlFiles, healthCheckPolicyFiles, fail) {
280
+ if (healthCheckPolicyFiles.length === 0) {
281
+ return
282
+ }
283
+
284
+ const ruAbsList = yamlFiles.filter(abs => isRuKustomizationPath(relative(root, abs) || abs))
285
+ if (ruAbsList.length === 0) {
286
+ fail(
287
+ `Знайдено HealthCheckPolicy у ${healthCheckPolicyFiles.join(', ')} — додай ru/kustomization.yaml з patch видалення (див. k8s.mdc)`
288
+ )
289
+ return
290
+ }
291
+
292
+ for (const abs of ruAbsList) {
293
+ let raw
294
+ try {
295
+ raw = await readFile(abs, 'utf8')
296
+ } catch (error) {
297
+ const msg = error instanceof Error ? error.message : String(error)
298
+ fail(`${relative(root, abs) || abs}: не вдалося прочитати (${msg})`)
299
+ return
300
+ }
301
+ if (ruKustomizationHasHealthCheckDeletePatch(raw)) {
302
+ return
303
+ }
304
+ }
305
+
306
+ fail(
307
+ 'Є HealthCheckPolicy, але жоден ru/kustomization.yaml не містить очікуваного patch видалення (kind: HealthCheckPolicy, metadata.name, $patch: delete) — див. k8s.mdc'
308
+ )
309
+ }
310
+
208
311
  /**
209
312
  * Чи порушує маніфест вимогу **`Deployment.spec.template.spec.containers[].resources`** (див. k8s.mdc).
210
313
  * @param {unknown} manifest корінь YAML-документа як об'єкт JavaScript
@@ -246,32 +349,104 @@ export function deploymentResourcesViolation(manifest) {
246
349
  }
247
350
 
248
351
  /**
249
- * Парсить усі YAML-документи з тіла файлу й реєструє порушення **`Deployment.resources`**.
250
- * @param {string} rel відносний шлях (для повідомлень)
352
+ * Чи контейнери **Deployment** мають **`imagePullPolicy: Always`** (k8s.mdc).
353
+ * @param {unknown} manifest корінь YAML-документа
354
+ * @returns {string | null} текст порушення або null, якщо не Deployment / ок
355
+ */
356
+ export function deploymentImagePullPolicyViolation(manifest) {
357
+ if (manifest === null || manifest === undefined || typeof manifest !== 'object' || Array.isArray(manifest))
358
+ return null
359
+ const rec = /** @type {Record<string, unknown>} */ (manifest)
360
+ if (rec.kind !== 'Deployment') return null
361
+ const spec = rec.spec
362
+ if (spec === null || spec === undefined || typeof spec !== 'object' || Array.isArray(spec)) return null
363
+ const template = /** @type {Record<string, unknown>} */ (spec).template
364
+ if (template === null || template === undefined || typeof template !== 'object' || Array.isArray(template))
365
+ return null
366
+ const podSpec = /** @type {Record<string, unknown>} */ (template).spec
367
+ if (podSpec === null || podSpec === undefined || typeof podSpec !== 'object' || Array.isArray(podSpec)) return null
368
+ const containers = /** @type {Record<string, unknown>} */ (podSpec).containers
369
+ if (!Array.isArray(containers)) return null
370
+
371
+ for (const [i, c] of containers.entries()) {
372
+ const label =
373
+ typeof c === 'object' && c !== null && !Array.isArray(c) && typeof c.name === 'string' && c.name !== ''
374
+ ? c.name
375
+ : `#${i + 1}`
376
+ if (c !== null && c !== undefined && typeof c === 'object' && !Array.isArray(c)) {
377
+ const cont = /** @type {Record<string, unknown>} */ (c)
378
+ if (cont.imagePullPolicy !== 'Always') {
379
+ return `контейнер "${label}": imagePullPolicy має бути Always (див. k8s.mdc)`
380
+ }
381
+ }
382
+ }
383
+
384
+ return null
385
+ }
386
+
387
+ /**
388
+ * У маніфестах ресурсів не має бути **metadata.namespace** — лише у **kustomization** (k8s.mdc).
389
+ * @param {unknown} manifest корінь YAML-документа
390
+ * @returns {string | null} текст порушення або null, якщо поля немає
391
+ */
392
+ export function metadataNamespaceForbiddenViolation(manifest) {
393
+ if (manifest === null || manifest === undefined || typeof manifest !== 'object' || Array.isArray(manifest))
394
+ return null
395
+ const rec = /** @type {Record<string, unknown>} */ (manifest)
396
+ const meta = rec.metadata
397
+ if (meta !== null && typeof meta === 'object' && !Array.isArray(meta) && 'namespace' in meta) {
398
+ return 'metadata.namespace заборонено — задай namespace у kustomization.yaml (поле namespace) (див. k8s.mdc)'
399
+ }
400
+ return null
401
+ }
402
+
403
+ /**
404
+ * Чи ім’я файлу — kustomization (дозволяє не застосовувати перевірку metadata.namespace до вмісту).
405
+ * @param {string} baseLower basename у нижньому регістрі
406
+ * @returns {boolean} true для `kustomization.yaml` / `kustomization.yml`
407
+ */
408
+ function isKustomizationFileName(baseLower) {
409
+ return baseLower === 'kustomization.yaml' || baseLower === 'kustomization.yml'
410
+ }
411
+
412
+ /**
413
+ * Парсить усі YAML-документи: **metadata.namespace**, **Deployment.resources**, **imagePullPolicy**.
414
+ * @param {string} rel відносний шлях
415
+ * @param {string} baseLower basename файлу (нижній регістр)
251
416
  * @param {string} body вміст після modeline
252
417
  * @param {(msg: string) => void} fail реєстрація помилки
253
418
  */
254
- function validateDeploymentResourcesInK8sYaml(rel, body, fail) {
419
+ function validateK8sYamlPolicyDocuments(rel, baseLower, body, fail) {
255
420
  /** @type {import('yaml').Document[]} */
256
421
  let docs
257
422
  try {
258
423
  docs = parseAllDocuments(body)
259
424
  } catch (error) {
260
425
  const msg = error instanceof Error ? error.message : String(error)
261
- fail(
262
- `${rel}: не вдалося розібрати YAML для перевірки Deployment.spec.template.spec.containers[].resources (${msg})`
263
- )
426
+ fail(`${rel}: не вдалося розібрати YAML для перевірок маніфестів (${msg})`)
264
427
  return
265
428
  }
266
429
 
430
+ const skipMetaNs = isKustomizationFileName(baseLower)
431
+
267
432
  for (const [di, doc] of docs.entries()) {
268
433
  if (doc.errors.length > 0) {
269
434
  fail(`${rel}: YAML (документ ${di + 1}): ${doc.errors.map(e => e.message).join('; ')}`)
270
435
  } else {
271
436
  const obj = doc.toJSON()
272
- const v = deploymentResourcesViolation(obj)
273
- if (v !== null) {
274
- fail(`${rel}: Deployment (документ ${di + 1}): ${v}`)
437
+ if (!skipMetaNs) {
438
+ const ns = metadataNamespaceForbiddenViolation(obj)
439
+ if (ns !== null) {
440
+ fail(`${rel}: документ ${di + 1}: ${ns}`)
441
+ }
442
+ }
443
+ const resV = deploymentResourcesViolation(obj)
444
+ if (resV !== null) {
445
+ fail(`${rel}: Deployment (документ ${di + 1}): ${resV}`)
446
+ }
447
+ const pullV = deploymentImagePullPolicyViolation(obj)
448
+ if (pullV !== null) {
449
+ fail(`${rel}: Deployment (документ ${di + 1}): ${pullV}`)
275
450
  }
276
451
  }
277
452
  }
@@ -358,9 +533,10 @@ function countSchemaModelines(lines) {
358
533
  * @param {string} root корінь репозиторію
359
534
  * @param {(msg: string) => void} fail реєстрація помилки
360
535
  * @param {(msg: string) => void} pass реєстрація успіху
536
+ * @param {string[]} healthCheckPolicyFiles накопичувач файлів із kind: HealthCheckPolicy
361
537
  * @returns {Promise<void>}
362
538
  */
363
- async function checkK8sYamlFile(abs, root, fail, pass) {
539
+ async function checkK8sYamlFile(abs, root, fail, pass, healthCheckPolicyFiles) {
364
540
  const rel = relative(root, abs) || abs
365
541
  const base = basename(abs)
366
542
  const baseLower = base.toLowerCase()
@@ -398,6 +574,8 @@ async function checkK8sYamlFile(abs, root, fail, pass) {
398
574
 
399
575
  const body = yamlBodyAfterModeline(lines)
400
576
 
577
+ scanIngressAndHealthCheckPolicy(rel, body, fail, healthCheckPolicyFiles)
578
+
401
579
  if (schemaUrl.startsWith('file:')) {
402
580
  pass(`${rel}: локальна схема (file:) — перевірка URL за apiVersion/kind пропущена`)
403
581
  } else if (/^https:/iu.test(schemaUrl)) {
@@ -420,7 +598,7 @@ async function checkK8sYamlFile(abs, root, fail, pass) {
420
598
  return
421
599
  }
422
600
 
423
- validateDeploymentResourcesInK8sYaml(rel, body, fail)
601
+ validateK8sYamlPolicyDocuments(rel, baseLower, body, fail)
424
602
  }
425
603
 
426
604
  /**
@@ -444,9 +622,14 @@ export async function check() {
444
622
 
445
623
  pass(`YAML у k8s: ${yamlFiles.length} файл(ів)`)
446
624
 
625
+ /** @type {string[]} */
626
+ const healthCheckPolicyFiles = []
627
+
447
628
  for (const abs of yamlFiles) {
448
- await checkK8sYamlFile(abs, root, fail, pass)
629
+ await checkK8sYamlFile(abs, root, fail, pass, healthCheckPolicyFiles)
449
630
  }
450
631
 
632
+ await ensureRuKustomizationHealthCheckDelete(root, yamlFiles, healthCheckPolicyFiles, fail)
633
+
451
634
  return exitCode
452
635
  }