@nitra/cursor 1.8.22 → 1.8.24
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 +0 -1
- package/mdc/k8s.mdc +6 -8
- package/package.json +1 -1
- package/scripts/check-k8s.mjs +99 -1
package/README.md
CHANGED
|
@@ -38,7 +38,6 @@
|
|
|
38
38
|
Коротко:
|
|
39
39
|
|
|
40
40
|
- **Структура Kustomize:** спільне виноситься в **`base`**; вміст **base** відповідає тому, як має виглядати середовище **dev**; окремої директорії **`dev/`** немає — за dev відповідає **`base`**. У інших середовищах — тонкі **overlays** (часто лише **`kustomization.yaml`** і patches / оверрайди).
|
|
41
|
-
- У каталозі **`k8s`** має бути **`README.md`** з описом дерев і застосування.
|
|
42
41
|
- **Namespace** задається в **`kustomization.yaml`** (`namespace:`), а не через **`metadata.namespace`** у кожному ресурсі; окремі patches лише на зміну **namespace** не потрібні.
|
|
43
42
|
- У **Deployment** для кожного контейнера: **`resources`**, **`imagePullPolicy: Always`** (перевіряє **`npx @nitra/cursor check k8s`**).
|
|
44
43
|
- Рядки в **base**, які змінюються в overlays, позначайте коментарем на рядку (узгоджено в команді), наприклад: `# буде замінено через kustomize`.
|
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.18'
|
|
4
4
|
globs: "**/k8s/**/*.{yaml,yml}"
|
|
5
5
|
alwaysApply: false
|
|
6
6
|
---
|
|
@@ -145,7 +145,7 @@ resources: {}
|
|
|
145
145
|
### Namespace
|
|
146
146
|
|
|
147
147
|
- У **`base/kustomization.yaml`** задай **`namespace: dev`** (або узгоджене ім’я для dev **namespace**), щоб **усі ресурси base** потрапляли в цей namespace через Kustomize.
|
|
148
|
-
|
|
148
|
+
|
|
149
149
|
- **Не додавай** окремі **patches** Kustomize, які лише змінюють **namespace**: **namespace** визначає Kustomize; у overlays додаткові зміни — без дублювання логіки **namespace**.
|
|
150
150
|
|
|
151
151
|
### Рядки, що змінюються між середовищами
|
|
@@ -158,10 +158,6 @@ resources: {}
|
|
|
158
158
|
|
|
159
159
|
Текст коментаря узгодь у команді; важливо, щоб було видно, що значення **навіть у base** може бути замінене overlay.
|
|
160
160
|
|
|
161
|
-
### Документація в дереві k8s
|
|
162
|
-
|
|
163
|
-
- У каталозі **`k8s`** (корінь оголошеного дерева маніфестів) має бути **`README.md`**: коротко опиши структуру (`base`, overlays), як зібрати/застосувати середовища, посилання на внутрішні правила команди.
|
|
164
|
-
|
|
165
161
|
### Міграція зі старої структури
|
|
166
162
|
|
|
167
163
|
- Після перенесення маніфестів у **`base`** та overlays і перевірки (**`check k8s`**, **`lint-k8s`**) **видали** застарілі файли та директорії, які замінені новою схемою (дубльовані копії, колишні шляхи без Kustomize), щоб у репозиторії не залишалося зайвих або суперечливих маніфестів.
|
|
@@ -199,7 +195,7 @@ resources: {}
|
|
|
199
195
|
|
|
200
196
|
## Перевірка
|
|
201
197
|
|
|
202
|
-
**`npx @nitra/cursor check k8s`** — узгодженість першого рядка (`$schema`), один рядок `# yaml-language-server` на файл, правило для `.yml`, відповідність URL першому YAML-документу (до `---`) за логікою нижче; для кожного документа **`Deployment`** — **`containers[].resources`**, **`imagePullPolicy: Always`**;
|
|
198
|
+
**`npx @nitra/cursor check k8s`** — узгодженість першого рядка (`$schema`), один рядок `# yaml-language-server` на файл, правило для `.yml`, відповідність URL першому YAML-документу (до `---`) за логікою нижче; для кожного документа **`Deployment`** — **`containers[].resources`**, **`imagePullPolicy: Always`**; заборона **`kind: Ingress`**; наявність **`HealthCheckPolicy`** — вимога до **`ru/kustomization.yaml`** з **`$patch: delete`** (див. **Ingress → Gateway API**); заборона шляхів **`…/k8s/dev/…`**; якщо існує **`k8s/base/kustomization.yaml`** (або **`.yml`**) — непорожній **`namespace`** у першому документі. Якщо під `k8s` немає yaml/yml — перевірку пропущено. Інший зміст маніфесту — вручну / **`lint-k8s`**.
|
|
203
199
|
|
|
204
200
|
Після змін у маніфестах: **`bun run lint-k8s`** (kubeconform + kubescape; обов'язково для проєктів з правилом **`k8s`** у **`.n-cursor.json`**).
|
|
205
201
|
|
|
@@ -213,13 +209,15 @@ resources: {}
|
|
|
213
209
|
- У файлах, ім’я яких **не** `kustomization.yaml` / `kustomization.yml`, у кожному документі — заборона поля **`metadata.namespace`** (**namespace** задається в Kustomize).
|
|
214
210
|
- Документи з **`kind: Ingress`** — заборонені (потрібен перехід на Gateway API, див. розділ **Ingress → Gateway API**).
|
|
215
211
|
- Якщо в будь-якому файлі під **`k8s`** є **`kind: HealthCheckPolicy`**, серед файлів має бути **`ru/kustomization.yaml`** (сегмент шляху **`ru`** перед іменем файлу), а його вміст — patch видалення **HealthCheckPolicy** з **`$patch: delete`** (див. той самий розділ).
|
|
212
|
+
- Заборона шляхів **`…/k8s/dev/…`** (окремої директорії **`dev`** під **`k8s`** не має бути).
|
|
213
|
+
- Якщо існує **`k8s/base/kustomization.yaml`** (або **`.yml`**), у першому документі має бути непорожнє поле **`namespace`** (типово **`dev`**; див. розділ **Namespace**).
|
|
216
214
|
|
|
217
215
|
## Коли застосовувати (агентам)
|
|
218
216
|
|
|
219
217
|
- Зміни в k8s YAML — після правок **`check k8s`**.
|
|
220
218
|
- Якщо перший рядок уже коректний і URL відповідає `apiVersion` / `kind` — не дублюй; змінився ресурс — онови лише `$schema`.
|
|
221
219
|
- У **`Deployment`** без поля **`resources`** у контейнері — додай **`resources: {}`** (див. розділ **Deployment: `resources`**); додай **`imagePullPolicy: Always`** для кожного контейнера.
|
|
222
|
-
- Дотримуйся структури **Kustomize** (`base` = dev, overlays без дублювання `base`,
|
|
220
|
+
- Дотримуйся структури **Kustomize** (`base` = dev, overlays без дублювання `base`, коментарі для рядків, що змінюються в overlay).
|
|
223
221
|
- Після міграції на нову структуру **видали** застарілі файли та каталоги, які вже замінені (див. **Міграція зі старої структури**).
|
|
224
222
|
|
|
225
223
|
## Визначення схеми YAML (канон)
|
package/package.json
CHANGED
package/scripts/check-k8s.mjs
CHANGED
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
* **`kind: Ingress`** заборонено (потрібен перехід на Gateway API). Якщо є **`HealthCheckPolicy`**,
|
|
16
16
|
* має існувати **`ru/kustomization.yaml`** з patch видалення цього kind (`$patch: delete`).
|
|
17
17
|
*
|
|
18
|
+
* Структура **Kustomize** (див. k8s.mdc): заборона шляхів **`…/k8s/dev/…`**; якщо є **`…/k8s/base/kustomization.yaml`**
|
|
19
|
+
* (або **`.yml`**), у першому документі має бути непорожнє поле **`namespace`**.
|
|
20
|
+
*
|
|
18
21
|
* Явні винятки до загальної логіки yannh/datree — таблиця **`EXPLICIT_K8S_SCHEMAS`** (`Map`): ключ
|
|
19
22
|
* **`apiVersion`, `kind`, `type`** (для CRD без поля `type` у маніфесті — зірочка **`*`** як третій
|
|
20
23
|
* компонент). Спочатку шукається збіг за фактичним `type`, потім за **`*`**.
|
|
@@ -150,6 +153,43 @@ export function pathHasK8sSegment(filePath) {
|
|
|
150
153
|
return parts.includes('k8s')
|
|
151
154
|
}
|
|
152
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Чи заборонений шлях з окремою директорією **`dev`** під **`k8s`** (джерело правди — **`base`**).
|
|
158
|
+
* @param {string} rel шлях від кореня репозиторію
|
|
159
|
+
* @returns {boolean} true для `…/k8s/dev/…`
|
|
160
|
+
*/
|
|
161
|
+
export function isForbiddenK8sDevPath(rel) {
|
|
162
|
+
const n = rel.replaceAll('\\', '/')
|
|
163
|
+
return n.includes('/k8s/dev/')
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Чи це **`k8s/base/kustomization.yaml`** або **`kustomization.yml`** (перевірка поля **`namespace`**).
|
|
168
|
+
* @param {string} rel шлях від кореня репозиторію
|
|
169
|
+
* @returns {boolean} true, якщо це `…/k8s/base/kustomization.yaml` або `…/k8s/base/kustomization.yml`
|
|
170
|
+
*/
|
|
171
|
+
export function isBaseKustomizationPath(rel) {
|
|
172
|
+
const n = rel.replaceAll('\\', '/')
|
|
173
|
+
return /(^|\/)k8s\/base\/kustomization\.yaml$/u.test(n) || /(^|\/)k8s\/base\/kustomization\.yml$/u.test(n)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Чи коректне поле **`namespace`** у розібраному Kustomization для **`base`**.
|
|
178
|
+
* @param {unknown} obj перший документ YAML
|
|
179
|
+
* @returns {string | null} текст порушення або null, якщо ок
|
|
180
|
+
*/
|
|
181
|
+
export function baseKustomizationNamespaceViolation(obj) {
|
|
182
|
+
if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
|
|
183
|
+
return 'у base/kustomization.yaml має бути непорожній namespace (див. k8s.mdc)'
|
|
184
|
+
}
|
|
185
|
+
const rec = /** @type {Record<string, unknown>} */ (obj)
|
|
186
|
+
const ns = rec.namespace
|
|
187
|
+
if (typeof ns === 'string' && ns.trim() !== '') {
|
|
188
|
+
return null
|
|
189
|
+
}
|
|
190
|
+
return 'у base/kustomization.yaml має бути непорожній namespace (наприклад namespace: dev; див. k8s.mdc)'
|
|
191
|
+
}
|
|
192
|
+
|
|
153
193
|
/**
|
|
154
194
|
* Збирає всі yaml/yml під деревом від кореня cwd, якщо шлях містить сегмент `k8s`.
|
|
155
195
|
* @param {string} root корінь репозиторію (cwd)
|
|
@@ -163,7 +203,8 @@ async function findK8sYamlFiles(root) {
|
|
|
163
203
|
if (!/\.ya?ml$/iu.test(p)) return
|
|
164
204
|
out.push(p)
|
|
165
205
|
})
|
|
166
|
-
|
|
206
|
+
// eslint-disable-next-line unicorn/no-array-sort -- toSorted потребує lib ES2023 у перевірці типів IDE
|
|
207
|
+
return [...out].sort((a, b) => a.localeCompare(b))
|
|
167
208
|
}
|
|
168
209
|
|
|
169
210
|
/**
|
|
@@ -601,6 +642,59 @@ async function checkK8sYamlFile(abs, root, fail, pass, healthCheckPolicyFiles) {
|
|
|
601
642
|
validateK8sYamlPolicyDocuments(rel, baseLower, body, fail)
|
|
602
643
|
}
|
|
603
644
|
|
|
645
|
+
/**
|
|
646
|
+
* Реєструє порушення для шляхів виду **`…/k8s/dev/…`** (окремої директорії **dev** не має бути).
|
|
647
|
+
* @param {string[]} yamlFiles абсолютні шляхи
|
|
648
|
+
* @param {string} root корінь репозиторію
|
|
649
|
+
* @param {(msg: string) => void} fail callback для реєстрації порушення
|
|
650
|
+
* @returns {void}
|
|
651
|
+
*/
|
|
652
|
+
function assertNoForbiddenK8sDevPaths(yamlFiles, root, fail) {
|
|
653
|
+
for (const abs of yamlFiles) {
|
|
654
|
+
const rel = relative(root, abs).replaceAll('\\', '/')
|
|
655
|
+
if (isForbiddenK8sDevPath(rel)) {
|
|
656
|
+
fail(`${rel}: заборонена директорія k8s/dev/ — середовище dev відповідає base (див. k8s.mdc)`)
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Якщо є **`k8s/base/kustomization.yaml`**, у ньому має бути непорожній **`namespace`**.
|
|
663
|
+
* @param {string} root корінь репозиторію
|
|
664
|
+
* @param {string[]} yamlFiles абсолютні шляхи
|
|
665
|
+
* @param {(msg: string) => void} fail callback для реєстрації порушення
|
|
666
|
+
* @returns {Promise<void>}
|
|
667
|
+
*/
|
|
668
|
+
async function ensureBaseKustomizationHasNamespace(root, yamlFiles, fail) {
|
|
669
|
+
for (const abs of yamlFiles) {
|
|
670
|
+
const rel = relative(root, abs).replaceAll('\\', '/')
|
|
671
|
+
if (isBaseKustomizationPath(rel)) {
|
|
672
|
+
try {
|
|
673
|
+
const raw = await readFile(abs, 'utf8')
|
|
674
|
+
const lines = toLines(raw)
|
|
675
|
+
const body = yamlBodyAfterModeline(lines)
|
|
676
|
+
/** @type {import('yaml').Document[] | undefined} */
|
|
677
|
+
let docs
|
|
678
|
+
try {
|
|
679
|
+
docs = parseAllDocuments(body)
|
|
680
|
+
} catch {
|
|
681
|
+
fail(`${rel}: не вдалося розпарсити YAML для перевірки namespace у base (див. k8s.mdc)`)
|
|
682
|
+
}
|
|
683
|
+
if (docs !== undefined) {
|
|
684
|
+
const first = docs[0]?.toJSON()
|
|
685
|
+
const v = baseKustomizationNamespaceViolation(first)
|
|
686
|
+
if (v) {
|
|
687
|
+
fail(`${rel}: ${v}`)
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
} catch (error) {
|
|
691
|
+
const msg = error instanceof Error ? error.message : String(error)
|
|
692
|
+
fail(`${rel}: не вдалося прочитати (${msg})`)
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
604
698
|
/**
|
|
605
699
|
* Перевіряє відповідність проєкту правилам k8s.mdc.
|
|
606
700
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
@@ -622,6 +716,8 @@ export async function check() {
|
|
|
622
716
|
|
|
623
717
|
pass(`YAML у k8s: ${yamlFiles.length} файл(ів)`)
|
|
624
718
|
|
|
719
|
+
assertNoForbiddenK8sDevPaths(yamlFiles, root, fail)
|
|
720
|
+
|
|
625
721
|
/** @type {string[]} */
|
|
626
722
|
const healthCheckPolicyFiles = []
|
|
627
723
|
|
|
@@ -631,5 +727,7 @@ export async function check() {
|
|
|
631
727
|
|
|
632
728
|
await ensureRuKustomizationHealthCheckDelete(root, yamlFiles, healthCheckPolicyFiles, fail)
|
|
633
729
|
|
|
730
|
+
await ensureBaseKustomizationHasNamespace(root, yamlFiles, fail)
|
|
731
|
+
|
|
634
732
|
return exitCode
|
|
635
733
|
}
|