@nitra/cursor 1.8.228 → 1.9.0
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/CHANGELOG.md +12 -0
- package/mdc/changelog.mdc +3 -2
- package/mdc/ga.mdc +3 -2
- package/mdc/graphql.mdc +3 -2
- package/mdc/hasura.mdc +3 -2
- package/mdc/image-avif.mdc +3 -2
- package/mdc/image-compress.mdc +3 -2
- package/mdc/k8s.mdc +1 -3
- package/mdc/nginx-default-tpl.mdc +3 -1
- package/mdc/php.mdc +3 -2
- package/mdc/style-lint.mdc +3 -2
- package/mdc/vue.mdc +3 -2
- package/package.json +1 -1
- package/policy/k8s/kustomization/kustomization.rego +2 -2
- package/policy/k8s/manifest/manifest.rego +4 -2
- package/scripts/check-k8s.mjs +15 -149
- package/scripts/utils/run-conftest-batch.mjs +1 -1
- package/policy/k8s/kustomize_managed/kustomize_managed.rego +0 -31
- package/policy/k8s/kustomize_managed/kustomize_managed_test.rego +0 -30
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
|
|
6
6
|
|
|
7
|
+
## [1.9.0] - 2026-05-11
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **mdc frontmatter — `alwaysApply: false` + `globs` для файлово-чітких правил:** `ga` (`.github/workflows/*.yml`), `vue` (`**/*.vue`), `php` (`**/*.php`), `style-lint` (`**/*.{css,scss,vue}`), `nginx-default-tpl` (`**/default.{conf.template,tpl.conf}`), `image-avif` (`**/*.{png,jpg,jpeg,gif,avif,vue,html}`), `image-compress` (`**/*.{png,jpg,jpeg,gif,svg}`), `changelog` (`**/{CHANGELOG.md,package.json}`), `hasura` (`**/hasura/**,**/*.env`), `graphql` (`**/*.{vue,js,mjs,cjs,ts,tsx,jsx}`). Раніше тільки `docker`, `k8s`, `rego` тримали file-scoped формат; решта вантажилася в контекст Cursor завжди (`alwaysApply: true`). Тепер правило підтягується лише коли в контексті є файл за патерном — менше «шуму» у промптах для несуміжних задач. Версії bump-нуто на патч-крок у кожному `*.mdc`. Проєктно-широкі правила (`bun`, `npm-module`, `ci4`, `text`, `js-lint`) і opt-in (`abie`, `adr`) лишилися `alwaysApply: true` без globs.
|
|
12
|
+
|
|
13
|
+
## [1.8.229] - 2026-05-11
|
|
14
|
+
|
|
15
|
+
### Removed
|
|
16
|
+
|
|
17
|
+
- **k8s / `k8s.kustomize_managed`:** правило «`metadata.namespace` заборонено у YAML, досяжних через граф Kustomize» зняте — воно конфліктувало з `k8s.base_manifest`, який натомість **вимагає** `metadata.namespace` у `…/k8s/base/…` для namespaced kind. Перетин предикатів був порожній, що давало ~50 хибних помилок у канонічних деревах `base + overlays` (adminer, run/nginx, reference-grant, otel, dremio, gateway тощо). Видалено: правило з `mdc/k8s.mdc` (бульйт «Де не дублювати `metadata.namespace`»), rego-полісь `npm/policy/k8s/kustomize_managed/`, JS-helpers `metadataNamespaceForbiddenViolation` і `collectKustomizeManagedRelPaths` разом з відповідними тестами та плумінгом `kustomizeManagedRel` через `runAllK8sRego` / `checkK8sYamlFile`. Логіка `base_manifest` (`metadata.namespace` обов'язковий у `k8s/base/`) лишається; у overlays Kustomize це значення буде перезаписано полем `namespace:` з `kustomization.yaml`.
|
|
18
|
+
|
|
7
19
|
## [1.8.228] - 2026-05-10
|
|
8
20
|
|
|
9
21
|
### Changed
|
package/mdc/changelog.mdc
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: CHANGELOG.md в кожному workspace, з двома моделями бази порівняння
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
version: '2.1'
|
|
4
|
+
globs: "**/{CHANGELOG.md,package.json}"
|
|
5
|
+
alwaysApply: false
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
Bun monorepo: у кожному workspace із кореневого `package.json.workspaces` (плюс кореневий пакет, плюс `npm/`) має бути власний **`CHANGELOG.md`**. Спільного на репозиторій змісту змін **не існує** — кожен пакет веде свій. Правило `npm-module` відповідає лише за публікацію типів і workflow, а CHANGELOG — за цим правилом.
|
package/mdc/ga.mdc
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Правила форматів для .github/workflows
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
version: '1.8'
|
|
4
|
+
globs: ".github/workflows/*.yml"
|
|
5
|
+
alwaysApply: false
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
У `.github/workflows/` лише **`.yml`**. Мають бути **`clean-ga-workflows.yml`**, **`clean-merged-branch.yml`**, **`lint-ga.yml`**, **`git-ai.yml`**. Якщо є **`apply-k8s.yml`** / **`apply-nats-consumer.yml`** — paths у тригері як у фрагментах.
|
package/mdc/graphql.mdc
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: GraphQL у коді (tagged template `gql`) — GraphQL Config і розширення VS Code
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
version: '1.1'
|
|
4
|
+
globs: "**/*.{vue,js,mjs,cjs,ts,tsx,jsx}"
|
|
5
|
+
alwaysApply: false
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
Якщо в **`.vue`** або в **JavaScript / TypeScript** джерелах (`.js`, `.mjs`, `.cjs`, `.ts`, `.tsx`, `.jsx` тощо) зустрічається **tagged template literal** з тегом **`gql`** (типово `gql\`query …\`` для GraphQL-запиту), у **корені репозиторію** мають бути:
|
package/mdc/hasura.mdc
CHANGED
package/mdc/image-avif.mdc
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: AVIF-двійники для raster-зображень з ув'язуванням у .vue/.html
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
version: '1.2'
|
|
4
|
+
globs: "**/*.{png,jpg,jpeg,gif,avif,vue,html}"
|
|
5
|
+
alwaysApply: false
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
AVIF-двійники (`<name>.<ext>.avif`) генерує **виключно** `npx @nitra/cursor check image-avif` — у `lint-image` прапорець `--avif` заборонений (це валідує правило `image-compress`). Перевірка робить три кроки в порядку:
|
package/mdc/image-compress.mdc
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Оптимізація raster/SVG через @nitra/minify-image у локальному lint
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
version: '1.2'
|
|
4
|
+
globs: "**/*.{png,jpg,jpeg,gif,svg}"
|
|
5
|
+
alwaysApply: false
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
CLI [`@nitra/minify-image`](https://www.npmjs.com/package/@nitra/minify-image) (≥ **3.3.1**) запускається через `npx` (як `markdownlint-cli2` у text.mdc) і **не** додається в `dependencies` / `devDependencies`. Канонічний `lint-image` — авто-оптимізація з прапорцем `--write`: стискає raster/SVG на місці. **AVIF-генерація (`--avif`) у `lint-image` заборонена** — її виконує окреме правило `image-avif` (`npx @nitra/cursor check image-avif`), яке заодно прибирає AVIF-сироти. Split-cache робить повторні прогони дешевими — і локально, і після `git clone`.
|
package/mdc/k8s.mdc
CHANGED
|
@@ -313,9 +313,7 @@ data:
|
|
|
313
313
|
|
|
314
314
|
- **`base/kustomization.yaml`:** поле **`namespace:`** має бути **непорожнім** (перевіряє **check k8s**, якщо файл є).
|
|
315
315
|
|
|
316
|
-
-
|
|
317
|
-
|
|
318
|
-
- **Коли `metadata.namespace` обов’язковий у файлі:** YAML під **`k8s`**, який **не** в графі жодного kustomization — непорожній **`metadata.namespace`** для namespaced **kind** (винятки — кластерні **kind**, перелік **`CLUSTER_SCOPED_KINDS`** у **`check-k8s.mjs`**). Якщо namespace у маніфесті не потрібен — підключи файл через **`resources`** / **`patches`** тощо.
|
|
316
|
+
- **Коли `metadata.namespace` обов’язковий у файлі:** YAML під **`k8s`** — непорожній **`metadata.namespace`** для namespaced **kind** (винятки — кластерні **kind**, перелік **`CLUSTER_SCOPED_KINDS`** у **`check-k8s.mjs`**). У overlays Kustomize значення в маніфесті буде перезаписано полем **`namespace:`** з відповідного **`kustomization.yaml`**, тому в `base` пиши канонічний dev-namespace.
|
|
319
317
|
|
|
320
318
|
- **Не додавай** окремі **patches** Kustomize, які лише змінюють **namespace**: **namespace** визначає Kustomize; у overlays додаткові зміни — без дублювання логіки **namespace**.
|
|
321
319
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Правила nginx для статичних файлів
|
|
3
|
-
version: '1.
|
|
3
|
+
version: '1.3'
|
|
4
|
+
globs: "**/default.{conf.template,tpl.conf}"
|
|
5
|
+
alwaysApply: false
|
|
4
6
|
---
|
|
5
7
|
|
|
6
8
|
> **Автоматична міграція:** `npx @nitra/cursor check nginx-default-tpl` автоматично перейменовує `default.tpl.conf` → `default.conf.template` (або перезаписує вміст, якщо обидва файли існують). Якщо шаблон відсутній — перевірка пропускається.
|
package/mdc/php.mdc
CHANGED
package/mdc/style-lint.mdc
CHANGED
package/mdc/vue.mdc
CHANGED
package/package.json
CHANGED
|
@@ -117,11 +117,11 @@ is_kustomization if {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
resources_present if {
|
|
120
|
-
|
|
120
|
+
"resources" in object.keys(input)
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
patches_present if {
|
|
124
|
-
|
|
124
|
+
"patches" in object.keys(input)
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
# Список непорожніх рядкових шляхів resources у порядку файлу (для повідомлення).
|
|
@@ -215,7 +215,8 @@ has_non_empty_cpu_request(container) if {
|
|
|
215
215
|
|
|
216
216
|
# Чи у контейнера в реальності присутнє поле resources.requests.cpu (хай і порожнє).
|
|
217
217
|
has_cpu_field(container) if {
|
|
218
|
-
|
|
218
|
+
requests := object.get(object.get(container, "resources", {}), "requests", {})
|
|
219
|
+
"cpu" in object.keys(requests)
|
|
219
220
|
}
|
|
220
221
|
|
|
221
222
|
# Чи у контейнера є непорожнє resources.requests.memory (рядок або число > 0).
|
|
@@ -233,7 +234,8 @@ has_non_empty_memory_request(container) if {
|
|
|
233
234
|
|
|
234
235
|
# Чи у контейнера в реальності присутнє поле resources.requests.memory.
|
|
235
236
|
has_memory_field(container) if {
|
|
236
|
-
|
|
237
|
+
requests := object.get(object.get(container, "resources", {}), "requests", {})
|
|
238
|
+
"memory" in object.keys(requests)
|
|
237
239
|
}
|
|
238
240
|
|
|
239
241
|
# Чи рядок `image` посилається на репозиторій `hasura/graphql-engine` (з тегом
|
package/scripts/check-k8s.mjs
CHANGED
|
@@ -360,18 +360,6 @@ export function isForbiddenK8sDevPath(rel) {
|
|
|
360
360
|
return n.includes('/k8s/dev/')
|
|
361
361
|
}
|
|
362
362
|
|
|
363
|
-
/**
|
|
364
|
-
* Відносний шлях від кореня репозиторію у вигляді з `/` (для множини kustomize).
|
|
365
|
-
* @param {string} root корінь cwd
|
|
366
|
-
* @param {string} abs абсолютний шлях
|
|
367
|
-
* @returns {string | null} posix-відносний шлях або null, якщо поза root
|
|
368
|
-
*/
|
|
369
|
-
function posixRelFromAbs(root, abs) {
|
|
370
|
-
const r = (relative(root, abs) || abs).replaceAll('\\', '/')
|
|
371
|
-
if (r.startsWith('..')) return null
|
|
372
|
-
return r
|
|
373
|
-
}
|
|
374
|
-
|
|
375
363
|
/**
|
|
376
364
|
* Вбудовані та поширені **кластерні** `kind`, для яких **`metadata.namespace`** не застосовується.
|
|
377
365
|
* CRD з невідомим kind лишаються з вимогою namespace, якщо файл не в kustomization — за потреби додай path у `resources`.
|
|
@@ -887,104 +875,6 @@ async function validateKustomizationIncludesSvcHlWithSvc(root, yamlFiles, fail)
|
|
|
887
875
|
}
|
|
888
876
|
}
|
|
889
877
|
|
|
890
|
-
/**
|
|
891
|
-
* Збирає відносні шляхи (posix) до YAML, підключених до Kustomize з будь-якого **`kustomization.yaml`** під `k8s`.
|
|
892
|
-
* Обходить **`resources`**, **`bases`**, **`components`**, **`crds`**, **`patches[].path`**, **`patchesStrategicMerge`**;
|
|
893
|
-
* для каталогу з **`kustomization.yaml`** виконує рекурсивний обхід.
|
|
894
|
-
* @param {string} root корінь репозиторію
|
|
895
|
-
* @param {string[]} yamlFilesAbs відсортовані абсолютні шляхи до `*.yaml` / `*.yml` під k8s (для `.yml` check-k8s вимагає перейменувати на `.yaml`)
|
|
896
|
-
* @returns {Promise<Set<string>>} множина відносних шляхів до керованих файлів
|
|
897
|
-
*/
|
|
898
|
-
export async function collectKustomizeManagedRelPaths(root, yamlFilesAbs) {
|
|
899
|
-
/** @type {Set<string>} */
|
|
900
|
-
const managed = new Set()
|
|
901
|
-
const kustomizationAbsList = yamlFilesAbs.filter(abs => {
|
|
902
|
-
const b = basename(abs).toLowerCase()
|
|
903
|
-
return b === 'kustomization.yaml'
|
|
904
|
-
})
|
|
905
|
-
|
|
906
|
-
/** @type {Set<string>} */
|
|
907
|
-
const visitedKustomization = new Set()
|
|
908
|
-
|
|
909
|
-
/**
|
|
910
|
-
* @param {string} kustAbs абсолютний шлях до kustomization.yaml
|
|
911
|
-
* @returns {Promise<void>}
|
|
912
|
-
*/
|
|
913
|
-
async function walkKustomization(kustAbs) {
|
|
914
|
-
const normKust = resolve(kustAbs)
|
|
915
|
-
if (visitedKustomization.has(normKust)) return
|
|
916
|
-
visitedKustomization.add(normKust)
|
|
917
|
-
|
|
918
|
-
let raw
|
|
919
|
-
try {
|
|
920
|
-
raw = await readFile(normKust, 'utf8')
|
|
921
|
-
} catch {
|
|
922
|
-
return
|
|
923
|
-
}
|
|
924
|
-
const lines = toLines(raw)
|
|
925
|
-
const body = yamlBodyAfterModeline(lines)
|
|
926
|
-
|
|
927
|
-
/** @type {import('yaml').Document[] | undefined} */
|
|
928
|
-
let docs
|
|
929
|
-
try {
|
|
930
|
-
docs = parseAllDocuments(body)
|
|
931
|
-
} catch {
|
|
932
|
-
return
|
|
933
|
-
}
|
|
934
|
-
const first = docs[0]?.toJSON()
|
|
935
|
-
if (first === null || first === undefined || typeof first !== 'object' || Array.isArray(first)) return
|
|
936
|
-
|
|
937
|
-
const kustDir = dirname(normKust)
|
|
938
|
-
const pathRefs = pathsFromKustomizationObject(first)
|
|
939
|
-
|
|
940
|
-
/**
|
|
941
|
-
* @param {string} ref шлях з kustomization
|
|
942
|
-
* @returns {Promise<void>}
|
|
943
|
-
*/
|
|
944
|
-
async function handleKustomizeManagedPathRef(ref) {
|
|
945
|
-
if (ref.includes('://')) {
|
|
946
|
-
return
|
|
947
|
-
}
|
|
948
|
-
const resolved = resolve(kustDir, ref)
|
|
949
|
-
let st
|
|
950
|
-
try {
|
|
951
|
-
st = await stat(resolved)
|
|
952
|
-
} catch {
|
|
953
|
-
st = undefined
|
|
954
|
-
}
|
|
955
|
-
if (!st) {
|
|
956
|
-
return
|
|
957
|
-
}
|
|
958
|
-
if (st.isFile()) {
|
|
959
|
-
if (YAML_EXTENSION_RE.test(resolved)) {
|
|
960
|
-
const pr = posixRelFromAbs(root, resolved)
|
|
961
|
-
if (pr !== null) {
|
|
962
|
-
managed.add(pr)
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
return
|
|
966
|
-
}
|
|
967
|
-
if (!st.isDirectory()) {
|
|
968
|
-
return
|
|
969
|
-
}
|
|
970
|
-
const childK = existsSync(join(resolved, 'kustomization.yaml')) ? join(resolved, 'kustomization.yaml') : null
|
|
971
|
-
if (childK !== null) {
|
|
972
|
-
await walkKustomization(childK)
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
for (const ref of pathRefs) {
|
|
977
|
-
await handleKustomizeManagedPathRef(ref)
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
for (const k of kustomizationAbsList) {
|
|
982
|
-
await walkKustomization(k)
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
return managed
|
|
986
|
-
}
|
|
987
|
-
|
|
988
878
|
/**
|
|
989
879
|
* Шляхи лише з полів ресурсів Kustomization (**без** patch-файлів).
|
|
990
880
|
* @param {unknown} obj корінь першого документа Kustomization
|
|
@@ -1331,7 +1221,7 @@ async function kustomizationTreeHasDeploymentUnderK8sBase(kustAbs, rootNorm) {
|
|
|
1331
1221
|
|
|
1332
1222
|
/**
|
|
1333
1223
|
* Збирає дескриптори ресурсів з **`resources` / `bases` / `components` / `crds`** для одного дерева kustomization.
|
|
1334
|
-
* Повторний вхід у той самий **`kustomization.yaml`** дає порожній
|
|
1224
|
+
* Повторний вхід у той самий **`kustomization.yaml`** дає порожній внесок.
|
|
1335
1225
|
* @param {string} kustAbs абсолютний шлях до **kustomization.yaml**
|
|
1336
1226
|
* @param {string} rootNorm нормалізований абсолютний корінь репозиторію
|
|
1337
1227
|
* @param {Set<string>} visitedKustomization нормалізовані абсолютні шляхи відвіданих **kustomization.yaml**
|
|
@@ -3505,22 +3395,6 @@ async function validateHasuraHttpRouteCanon(root, yamlFiles, fail) {
|
|
|
3505
3395
|
}
|
|
3506
3396
|
}
|
|
3507
3397
|
|
|
3508
|
-
/**
|
|
3509
|
-
* Для маніфестів, **підключених** до Kustomize (шлях у `resources` / `patches` / …), **metadata.namespace** не додають.
|
|
3510
|
-
* @param {unknown} manifest корінь YAML-документа
|
|
3511
|
-
* @returns {string | null} текст порушення або null, якщо поля немає
|
|
3512
|
-
*/
|
|
3513
|
-
export function metadataNamespaceForbiddenViolation(manifest) {
|
|
3514
|
-
if (manifest === null || manifest === undefined || typeof manifest !== 'object' || Array.isArray(manifest))
|
|
3515
|
-
return null
|
|
3516
|
-
const rec = /** @type {Record<string, unknown>} */ (manifest)
|
|
3517
|
-
const meta = rec.metadata
|
|
3518
|
-
if (meta !== null && typeof meta === 'object' && !Array.isArray(meta) && 'namespace' in meta) {
|
|
3519
|
-
return 'metadata.namespace заборонено — namespace задає kustomization.yaml (поле namespace); файл підключено через resources / patches / … (див. k8s.mdc)'
|
|
3520
|
-
}
|
|
3521
|
-
return null
|
|
3522
|
-
}
|
|
3523
|
-
|
|
3524
3398
|
/**
|
|
3525
3399
|
* Вимагає непорожній **metadata.namespace** для namespaced-документів (крім кластерних kind).
|
|
3526
3400
|
* @param {unknown} manifest корінь YAML-документа
|
|
@@ -3574,7 +3448,7 @@ export function isK8sBaseManifestYamlPath(rel, baseLower) {
|
|
|
3574
3448
|
|
|
3575
3449
|
// Plan B: per-document валідаційне ядро для k8s YAML повністю в rego —
|
|
3576
3450
|
// `k8s.manifest`, `k8s.gateway`, `k8s.svc_yaml`, `k8s.svc_hl_yaml`,
|
|
3577
|
-
// `k8s.
|
|
3451
|
+
// `k8s.base_manifest`. Виклик через `runAllK8sRego`.
|
|
3578
3452
|
// JS-функції failIfK8sPolicyNamespaceRulesViolated, failIfK8sPolicyResourceRulesViolated,
|
|
3579
3453
|
// validateK8sYamlPolicyDocuments видалено.
|
|
3580
3454
|
|
|
@@ -3679,13 +3553,12 @@ function countSchemaModelines(lines) {
|
|
|
3679
3553
|
* @param {string[]} lines рядки файлу
|
|
3680
3554
|
* @param {(msg: string) => void} fail реєстрація помилки
|
|
3681
3555
|
* @param {(msg: string) => void} pass реєстрація успіху
|
|
3682
|
-
* @param {Set<string>} kustomizeManagedRel kustomize-managed шляхи
|
|
3683
3556
|
* @returns {void}
|
|
3684
3557
|
*/
|
|
3685
|
-
function checkK8sYamlHttpBackendGroupFile(rel, _baseLower, _lines, _fail, pass
|
|
3558
|
+
function checkK8sYamlHttpBackendGroupFile(rel, _baseLower, _lines, _fail, pass) {
|
|
3686
3559
|
// Per-document валідація (Ingress/autoscaling/v1 заборонено, Gateway API backendRef,
|
|
3687
|
-
// metadata.namespace правила) — у rego (`k8s.manifest`, `k8s.gateway`, `k8s.
|
|
3688
|
-
//
|
|
3560
|
+
// metadata.namespace правила) — у rego (`k8s.manifest`, `k8s.gateway`, `k8s.base_manifest`),
|
|
3561
|
+
// батч-виклик з `runAllK8sRego` на початку `check()`.
|
|
3689
3562
|
pass(`${rel}: HttpBackendGroup (alb.yc.io/v1alpha1) — modeline $schema не застосовується (k8s.mdc)`)
|
|
3690
3563
|
}
|
|
3691
3564
|
|
|
@@ -3697,10 +3570,9 @@ function checkK8sYamlHttpBackendGroupFile(rel, _baseLower, _lines, _fail, pass,
|
|
|
3697
3570
|
* @param {string[]} lines рядки файлу
|
|
3698
3571
|
* @param {(msg: string) => void} fail реєстрація помилки
|
|
3699
3572
|
* @param {(msg: string) => void} pass реєстрація успіху
|
|
3700
|
-
* @param {Set<string>} kustomizeManagedRel kustomize-managed шляхи
|
|
3701
3573
|
* @returns {void}
|
|
3702
3574
|
*/
|
|
3703
|
-
function checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pass
|
|
3575
|
+
function checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pass) {
|
|
3704
3576
|
const match = lines[0].match(MODELINE_RE)
|
|
3705
3577
|
if (!match) {
|
|
3706
3578
|
fail(`${rel}: некоректний modeline $schema у першому рядку`)
|
|
@@ -3746,10 +3618,9 @@ function checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pa
|
|
|
3746
3618
|
* @param {string} root корінь репозиторію
|
|
3747
3619
|
* @param {(msg: string) => void} fail реєстрація помилки
|
|
3748
3620
|
* @param {(msg: string) => void} pass реєстрація успіху
|
|
3749
|
-
* @param {Set<string>} kustomizeManagedRel відносні posix-шляхи з collectKustomizeManagedRelPaths
|
|
3750
3621
|
* @returns {Promise<void>}
|
|
3751
3622
|
*/
|
|
3752
|
-
async function checkK8sYamlFile(abs, root, fail, pass
|
|
3623
|
+
async function checkK8sYamlFile(abs, root, fail, pass) {
|
|
3753
3624
|
const rel = (relative(root, abs) || abs).replaceAll('\\', '/')
|
|
3754
3625
|
const base = basename(abs)
|
|
3755
3626
|
const baseLower = base.toLowerCase()
|
|
@@ -3790,7 +3661,7 @@ async function checkK8sYamlFile(abs, root, fail, pass, kustomizeManagedRel) {
|
|
|
3790
3661
|
)
|
|
3791
3662
|
return
|
|
3792
3663
|
}
|
|
3793
|
-
checkK8sYamlHttpBackendGroupFile(rel, baseLower, lines, fail, pass
|
|
3664
|
+
checkK8sYamlHttpBackendGroupFile(rel, baseLower, lines, fail, pass)
|
|
3794
3665
|
return
|
|
3795
3666
|
}
|
|
3796
3667
|
|
|
@@ -3799,7 +3670,7 @@ async function checkK8sYamlFile(abs, root, fail, pass, kustomizeManagedRel) {
|
|
|
3799
3670
|
return
|
|
3800
3671
|
}
|
|
3801
3672
|
|
|
3802
|
-
checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pass
|
|
3673
|
+
checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pass)
|
|
3803
3674
|
}
|
|
3804
3675
|
|
|
3805
3676
|
/**
|
|
@@ -6030,19 +5901,18 @@ async function runKustomizationImagesCleanup(kustAbs, rel, fail, pass) {
|
|
|
6030
5901
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
6031
5902
|
*/
|
|
6032
5903
|
/**
|
|
6033
|
-
* Plan B (rego-authoritative): на початку `check()` батч-викликаємо
|
|
6034
|
-
* rego
|
|
5904
|
+
* Plan B (rego-authoritative): на початку `check()` батч-викликаємо path-фільтровані
|
|
5905
|
+
* rego-пакети з `npm/policy/k8s/` через `runConftestBatch`. Пакети hasura_configmap і
|
|
6035
5906
|
* hasura_httproute мають cross-file gating (паруються з Hasura-Deployment) — вони запускаються
|
|
6036
5907
|
* з відповідних orchestrator-функцій (`validateHasuraConfigMapRemoteSchemaPermissions`,
|
|
6037
5908
|
* `validateHasuraHttpRouteCanon`). Структурна частина HPA/PDB (`k8s.hpa_pdb`) тут на всіх yaml,
|
|
6038
5909
|
* env-залежні межі min/maxReplicas і expected-name — JS-cross-file у `validateDeploymentHpaPdbAndTopology`.
|
|
6039
5910
|
* @param {string} root корінь репозиторію (cwd)
|
|
6040
5911
|
* @param {string[]} yamlFiles абсолютні шляхи знайдених *.yaml під `…/k8s/`
|
|
6041
|
-
* @param {Set<string>} kustomizeManagedRel відносні posix-шляхи kustomize-managed файлів
|
|
6042
5912
|
* @param {(msg: string) => void} fail callback при помилці
|
|
6043
5913
|
* @returns {void}
|
|
6044
5914
|
*/
|
|
6045
|
-
function runAllK8sRego(root, yamlFiles,
|
|
5915
|
+
function runAllK8sRego(root, yamlFiles, fail) {
|
|
6046
5916
|
const relOf = abs => relative(root, abs).replaceAll('\\', '/') || abs
|
|
6047
5917
|
|
|
6048
5918
|
const allYaml = yamlFiles
|
|
@@ -6055,7 +5925,6 @@ function runAllK8sRego(root, yamlFiles, kustomizeManagedRel, fail) {
|
|
|
6055
5925
|
if (!K8S_BASE_SEGMENT_RE.test(r)) return false
|
|
6056
5926
|
return basename(p).toLowerCase() !== 'kustomization.yaml'
|
|
6057
5927
|
})
|
|
6058
|
-
const kustomizeManagedFiles = yamlFiles.filter(p => kustomizeManagedRel.has(relOf(p)))
|
|
6059
5928
|
|
|
6060
5929
|
/** @type {Array<{ ns: string, dir: string, files: string[] }>} */
|
|
6061
5930
|
const targets = [
|
|
@@ -6066,8 +5935,7 @@ function runAllK8sRego(root, yamlFiles, kustomizeManagedRel, fail) {
|
|
|
6066
5935
|
{ ns: 'k8s.svc_yaml', dir: 'k8s/svc_yaml', files: svcYaml },
|
|
6067
5936
|
{ ns: 'k8s.svc_hl_yaml', dir: 'k8s/svc_hl_yaml', files: svcHlYaml },
|
|
6068
5937
|
{ ns: 'k8s.base_kustomization', dir: 'k8s/base_kustomization', files: baseKustYaml },
|
|
6069
|
-
{ ns: 'k8s.base_manifest', dir: 'k8s/base_manifest', files: baseResourceYaml }
|
|
6070
|
-
{ ns: 'k8s.kustomize_managed', dir: 'k8s/kustomize_managed', files: kustomizeManagedFiles }
|
|
5938
|
+
{ ns: 'k8s.base_manifest', dir: 'k8s/base_manifest', files: baseResourceYaml }
|
|
6071
5939
|
]
|
|
6072
5940
|
|
|
6073
5941
|
for (const t of targets) {
|
|
@@ -6103,16 +5971,14 @@ export async function check() {
|
|
|
6103
5971
|
|
|
6104
5972
|
assertNoForbiddenK8sDevPaths(yamlFiles, root, fail)
|
|
6105
5973
|
|
|
6106
|
-
const kustomizeManagedRel = await collectKustomizeManagedRelPaths(root, yamlFiles)
|
|
6107
|
-
|
|
6108
5974
|
// Plan B: пер-документні структурні правила — у rego-полісі `npm/policy/k8s/*`,
|
|
6109
5975
|
// викликаємо одним батчем на namespace через runConftestBatch. JS нижче робить
|
|
6110
5976
|
// лише cross-file orchestration, modeline та FS-existence перевірки.
|
|
6111
|
-
runAllK8sRego(root, yamlFiles,
|
|
5977
|
+
runAllK8sRego(root, yamlFiles, fail)
|
|
6112
5978
|
pass(`Rego-полісі (npm/policy/k8s/*) виконано на ${yamlFiles.length} файл(ах)`)
|
|
6113
5979
|
|
|
6114
5980
|
for (const abs of yamlFiles) {
|
|
6115
|
-
await checkK8sYamlFile(abs, root, fail, pass
|
|
5981
|
+
await checkK8sYamlFile(abs, root, fail, pass)
|
|
6116
5982
|
}
|
|
6117
5983
|
|
|
6118
5984
|
await validateSvcYamlAndSvcHlPairs(root, yamlFiles, fail)
|
|
@@ -102,7 +102,7 @@ export function runConftestBatch(opts) {
|
|
|
102
102
|
let parsed
|
|
103
103
|
try {
|
|
104
104
|
parsed = JSON.parse(result.stdout)
|
|
105
|
-
} catch
|
|
105
|
+
} catch {
|
|
106
106
|
throw new Error(`conftest stdout не парситься як JSON: ${(result.stdout || '').slice(0, 200)}`)
|
|
107
107
|
}
|
|
108
108
|
/** @type {ConftestViolation[]} */
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# Порт перевірки `metadataNamespaceForbiddenViolation` з
|
|
2
|
-
# `npm/scripts/check-k8s.mjs` (k8s.mdc): для файлів, які підключено до якогось
|
|
3
|
-
# `kustomization.yaml` через `resources` / `patches` / `…`, поле
|
|
4
|
-
# `metadata.namespace` забороняється — namespace задає сам kustomization.
|
|
5
|
-
#
|
|
6
|
-
# Запуск (локально, лише для одного kustomize-managed YAML):
|
|
7
|
-
# conftest test path/to/manifest.yaml -p npm/policy/k8s/kustomize_managed \
|
|
8
|
-
# --namespace k8s.kustomize_managed
|
|
9
|
-
#
|
|
10
|
-
# JS відбирає kustomize-managed файли через `collectKustomizeManagedRelPaths`
|
|
11
|
-
# і викликає conftest з цією намеспейс. JS authoritative
|
|
12
|
-
# (`check-k8s.mjs`: `metadataNamespaceForbiddenViolation`,
|
|
13
|
-
# `failIfK8sPolicyNamespaceRulesViolated`).
|
|
14
|
-
#
|
|
15
|
-
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
16
|
-
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`.
|
|
17
|
-
package k8s.kustomize_managed
|
|
18
|
-
|
|
19
|
-
import rego.v1
|
|
20
|
-
|
|
21
|
-
namespace_forbidden_msg := concat(" ", [
|
|
22
|
-
"metadata.namespace заборонено — namespace задає kustomization.yaml",
|
|
23
|
-
"(поле namespace); файл підключено через resources / patches / …",
|
|
24
|
-
"(k8s.mdc)",
|
|
25
|
-
])
|
|
26
|
-
|
|
27
|
-
deny contains namespace_forbidden_msg if {
|
|
28
|
-
meta := object.get(input, "metadata", null)
|
|
29
|
-
is_object(meta)
|
|
30
|
-
"namespace" in object.keys(meta)
|
|
31
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# Тести для `k8s.kustomize_managed`. Запуск:
|
|
2
|
-
# conftest verify -p npm/policy/k8s/kustomize_managed --namespace k8s.kustomize_managed
|
|
3
|
-
package k8s.kustomize_managed_test
|
|
4
|
-
|
|
5
|
-
import rego.v1
|
|
6
|
-
|
|
7
|
-
import data.k8s.kustomize_managed
|
|
8
|
-
|
|
9
|
-
test_deny_metadata_with_namespace if {
|
|
10
|
-
count(kustomize_managed.deny) > 0 with input as {
|
|
11
|
-
"apiVersion": "v1",
|
|
12
|
-
"kind": "ConfigMap",
|
|
13
|
-
"metadata": {"name": "cm", "namespace": "dev"},
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
test_allow_metadata_without_namespace if {
|
|
18
|
-
count(kustomize_managed.deny) == 0 with input as {
|
|
19
|
-
"apiVersion": "v1",
|
|
20
|
-
"kind": "ConfigMap",
|
|
21
|
-
"metadata": {"name": "cm"},
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
test_allow_no_metadata if {
|
|
26
|
-
count(kustomize_managed.deny) == 0 with input as {
|
|
27
|
-
"apiVersion": "v1",
|
|
28
|
-
"kind": "ConfigMap",
|
|
29
|
-
}
|
|
30
|
-
}
|