@nitra/cursor 1.8.228 → 1.9.1
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 +22 -0
- package/mdc/changelog.mdc +3 -2
- package/mdc/ci4.mdc +48 -0
- 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/base_kustomization/base_kustomization.rego +30 -0
- package/policy/k8s/base_kustomization/base_kustomization_test.rego +37 -0
- package/policy/k8s/kustomization/kustomization.rego +2 -2
- package/policy/k8s/manifest/manifest.rego +4 -2
- package/scripts/check-k8s.mjs +17 -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,28 @@
|
|
|
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.1] - 2026-05-11
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **rego `k8s.base_kustomization` — defense-in-depth deny на HPA/PDB у `base/kustomization.yaml::resources:`:** додано пер-документне правило, що відмовляє, якщо `resources:` локально містить запис із basename `hpa.yaml`/`pdb.yaml`/`hpa.yml`/`pdb.yml` (у будь-якому підкаталозі). Канон k8s.mdc — HPA/PDB у sibling `components/` (Kustomize Component) і підключаються з overlay. Рекурсивний обхід дерева `resources:`/`components:`/`bases:` (із зануренням у вкладені kustomization.yaml) лишається у JS-оркестраторі `verifyK8sBaseKustomizeHasNoHpaPdb` (потребує fs-доступу). Rego-deny ловить найпоширеніший локальний випадок навіть якщо JS-крок упаде з винятку раніше. 5 нових rego-тестів (`hpa.yaml`/`pdb.yaml`/`hpa.yml` у `resources:`, чистий `resources:`, lookalike basename `myhpa.yaml`); `opa test` зелений (10/10).
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **`check-k8s.mjs`:** додано константу `GATEWAY_API_GROUP_PREFIX = 'gateway.networking.k8s.io/'`. Її відсутність кидала `ReferenceError` у `indexOneK8sYamlForHasuraCanon` (на лінії з `av.startsWith(GATEWAY_API_GROUP_PREFIX)`), яку ловив outer try/catch у `bin/n-cursor.js` і **тихо пропускав** усі наступні JS-валідатори в `check-k8s.mjs::check()` — серед них `validateKustomizeHpaPdbOnlyWithBaseDeployment`, `validateConfigMapNameMatchesDeployment`, `validateDeploymentHpaPdbAndTopology`, `validateProdKustomizationOverrides`. Наслідок у репах споживачів: правило «HPA/PDB заборонені у `k8s/base/`» не спрацьовувало (хоча `verifyK8sBaseKustomizeHasNoHpaPdb` логіку містив правильну), бо exception вилітав раніше за чергу JS-кроків. Rego-крок (`runAllK8sRego`) ішов **до** crash-точки й тому продовжував працювати — пер-документні перевірки залишалися активними, а cross-file JS — ні.
|
|
16
|
+
|
|
17
|
+
## [1.9.0] - 2026-05-11
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **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.
|
|
22
|
+
|
|
23
|
+
## [1.8.229] - 2026-05-11
|
|
24
|
+
|
|
25
|
+
### Removed
|
|
26
|
+
|
|
27
|
+
- **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`.
|
|
28
|
+
|
|
7
29
|
## [1.8.228] - 2026-05-10
|
|
8
30
|
|
|
9
31
|
### 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/ci4.mdc
CHANGED
|
@@ -15,6 +15,54 @@ C4-діаграми проєкту живуть у Markdown поряд із ко
|
|
|
15
15
|
тримати знання разом із кодом — версійно, в одному PR і доступно для агентів. Якщо щось
|
|
16
16
|
важливе про систему існує лише у Confluence/Notion/месенджері — для проєкту цього **немає**.
|
|
17
17
|
|
|
18
|
+
## Специфікація як джерело істини (Spec-as-Source)
|
|
19
|
+
|
|
20
|
+
У AI-native розробці первинним артефактом, який підтримують інженери, є **специфікація**, а не
|
|
21
|
+
кодова база. Код — похідний артефакт, «будівельне риштування», яке агент генерує, верифікує або
|
|
22
|
+
повністю відновлює зі специфікації. Якщо поведінка системи має змінитися — **спочатку оновлюємо
|
|
23
|
+
специфікацію (C4/ADR/опис компонента), і лише потім** агент генерує відповідний код. Зворотний
|
|
24
|
+
порядок («код вже написаний, доку напишемо потім») перетворює специфікацію на декорацію.
|
|
25
|
+
|
|
26
|
+
## Тест на повне відтворення (Rebuild Test)
|
|
27
|
+
|
|
28
|
+
Документація вважається повною лише тоді, коли проходить бінарний тест: якщо видалити теку
|
|
29
|
+
`src/`, відкрити нову LLM-сесію з чистим контекстом і дати агенту доступ лише до `.md`-файлів —
|
|
30
|
+
агент має **відтворити робочу кодову базу**. Архітектурні збої (агент не знає структуру тек),
|
|
31
|
+
інтеграційні (неописані міжсервісні контракти), падіння юніт-тестів (неописана бізнес-логіка) —
|
|
32
|
+
це не «складність задачі», а **прогалини документації**, які треба заповнити у тому ж PR.
|
|
33
|
+
|
|
34
|
+
## Ефективність токенів і чистий Markdown
|
|
35
|
+
|
|
36
|
+
Вікно контексту LLM обмежене, а якість міркування деградує в міру його заповнення (ефект
|
|
37
|
+
«Lost in the Middle»). Тому документація **очищається від HTML-розмітки, CSS-класів,
|
|
38
|
+
навігаційних обгорток** і всього, що не несе семантичного навантаження. Чистий Markdown замість
|
|
39
|
+
HTML економить 80–90 % токенів (≈16 000 → 1 600 на сторінку), що прямо впливає на точність
|
|
40
|
+
згенерованого коду і знижує вартість API. Жодних `<div>`/`<span>`/класів у тілі `.md`/`.mdc`.
|
|
41
|
+
|
|
42
|
+
## Контекстна незалежність розділів
|
|
43
|
+
|
|
44
|
+
RAG витягує **фрагменти**, не цілі документи. Тому кожен розділ має сенс **без сусіднього
|
|
45
|
+
тексту**. Заборонено: «як було згадано вище», «ця змінна», «попередній метод», «той самий
|
|
46
|
+
сервіс». Замість цього — щоразу явно повторюємо назву сутності: «автентифікація OAuth 2.0»,
|
|
47
|
+
«функція `calculateTotal()`», «контейнер `api-gateway`». Коли агент отримає лише цей фрагмент,
|
|
48
|
+
у нього має бути повний словник для коректної генерації.
|
|
49
|
+
|
|
50
|
+
## Docs-as-Code
|
|
51
|
+
|
|
52
|
+
Документація живе у Git поруч із кодом, проходить **той самий Code Review**, версіонується і
|
|
53
|
+
автоматично перевіряється у CI (лінтери Markdown, валідатори посилань, `npx @nitra/cursor
|
|
54
|
+
check`). Биті посилання й документація, що «не компілюється», — **блокуючий баг**, не
|
|
55
|
+
косметика. Це продовження принципу «оновлення синхронно зі змінами» нижче: один PR — код +
|
|
56
|
+
схема + ADR + тести.
|
|
57
|
+
|
|
58
|
+
## Трасування як документація недетермінованої поведінки
|
|
59
|
+
|
|
60
|
+
Для компонентів, де рішення приймає нейромережа або складна евристика, класичної форми
|
|
61
|
+
«вхід X → вихід Y» **недостатньо** — поведінка недетермінована. Джерелом істини стає
|
|
62
|
+
**пайплайн логування й трасування**: які інструменти використав агент, який контекст мав,
|
|
63
|
+
чому ухвалив це рішення. У C4-компоненті такого типу — посилання на дашборд/storage трасувань
|
|
64
|
+
обов'язкове нарівні з посиланнями на тести.
|
|
65
|
+
|
|
18
66
|
## Розташування
|
|
19
67
|
|
|
20
68
|
C4-діаграми проєкту живуть у теці `docs/ci4/` — це **канонічне місце** для всіх рівнів
|
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
|
@@ -34,7 +34,37 @@ deny contains base_namespace_required_msg if {
|
|
|
34
34
|
trim_space(ns) == ""
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
# HPA/PDB у base заборонені — канон k8s.mdc: тримати у sibling каталозі `components/`
|
|
38
|
+
# і підключати з overlay (`components: [- ../components]`). Цей deny — швидкий gate
|
|
39
|
+
# на *локальний* `resources:` base/kustomization.yaml (точне ім'я `hpa.yaml`/`pdb.yaml`,
|
|
40
|
+
# у будь-якому підкаталозі). Рекурсивний обхід `resources:`/`components:`/`bases:`
|
|
41
|
+
# (із зануренням у вкладені kustomization.yaml) — JS-оркестратор
|
|
42
|
+
# `verifyK8sBaseKustomizeHasNoHpaPdb` у `check-k8s.mjs` (потребує fs-доступу). Цей
|
|
43
|
+
# rego-deny — defense-in-depth: спрацює навіть якщо JS-крок упаде з винятку раніше.
|
|
44
|
+
deny contains hpa_pdb_in_base_resources_msg(r) if {
|
|
45
|
+
is_kustomization
|
|
46
|
+
some r in object.get(input, "resources", [])
|
|
47
|
+
is_string(r)
|
|
48
|
+
is_hpa_or_pdb_filename(r)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
hpa_pdb_in_base_resources_msg(file) := sprintf(
|
|
52
|
+
concat("", [
|
|
53
|
+
"у base/kustomization.yaml `resources:` містить '%v' — HPA/PDB заборонені у base, ",
|
|
54
|
+
"перенесіть у sibling каталог components/ і підключайте з overlay (k8s.mdc)",
|
|
55
|
+
]),
|
|
56
|
+
[file],
|
|
57
|
+
)
|
|
58
|
+
|
|
37
59
|
is_kustomization if {
|
|
38
60
|
input.kind == "Kustomization"
|
|
39
61
|
startswith(object.get(input, "apiVersion", ""), "kustomize.config.k8s.io/")
|
|
40
62
|
}
|
|
63
|
+
|
|
64
|
+
is_hpa_or_pdb_filename(p) if {
|
|
65
|
+
basename(p) in {"hpa.yaml", "pdb.yaml", "hpa.yml", "pdb.yml"}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
basename(p) := parts[count(parts) - 1] if {
|
|
69
|
+
parts := split(p, "/")
|
|
70
|
+
}
|
|
@@ -34,3 +34,40 @@ test_allow_non_kustomization if {
|
|
|
34
34
|
"metadata": {"name": "cm"},
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
+
|
|
38
|
+
base_kust_ok := object.union(base_kust, {"namespace": "dev"})
|
|
39
|
+
|
|
40
|
+
test_deny_hpa_yaml_in_resources if {
|
|
41
|
+
count(base_kustomization.deny) > 0 with input as object.union(
|
|
42
|
+
base_kust_ok,
|
|
43
|
+
{"resources": ["deployment.yaml", "hpa.yaml"]},
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
test_deny_pdb_yaml_in_resources if {
|
|
48
|
+
count(base_kustomization.deny) > 0 with input as object.union(
|
|
49
|
+
base_kust_ok,
|
|
50
|
+
{"resources": ["pdb.yaml"]},
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
test_deny_hpa_yml_in_subdir if {
|
|
55
|
+
count(base_kustomization.deny) > 0 with input as object.union(
|
|
56
|
+
base_kust_ok,
|
|
57
|
+
{"resources": ["nested/dir/hpa.yml"]},
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
test_allow_resources_without_hpa_pdb if {
|
|
62
|
+
count(base_kustomization.deny) == 0 with input as object.union(
|
|
63
|
+
base_kust_ok,
|
|
64
|
+
{"resources": ["deployment.yaml", "service.yaml", "configmap.yaml"]},
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
test_allow_lookalike_basename if {
|
|
69
|
+
count(base_kustomization.deny) == 0 with input as object.union(
|
|
70
|
+
base_kust_ok,
|
|
71
|
+
{"resources": ["myhpa.yaml", "pdb-extra.yaml"]},
|
|
72
|
+
)
|
|
73
|
+
}
|
|
@@ -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
|
@@ -309,6 +309,8 @@ const YANNH_GROUPS = new Set([
|
|
|
309
309
|
'storagemigration.k8s.io'
|
|
310
310
|
])
|
|
311
311
|
|
|
312
|
+
const GATEWAY_API_GROUP_PREFIX = 'gateway.networking.k8s.io/'
|
|
313
|
+
|
|
312
314
|
const MODELINE_RE = /^#\s*yaml-language-server:\s*\$schema=(\S+)\s*$/
|
|
313
315
|
const PATH_SPLIT_RE = /[/\\]/u
|
|
314
316
|
const YAML_EXTENSION_RE = /\.ya?ml$/iu
|
|
@@ -360,18 +362,6 @@ export function isForbiddenK8sDevPath(rel) {
|
|
|
360
362
|
return n.includes('/k8s/dev/')
|
|
361
363
|
}
|
|
362
364
|
|
|
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
365
|
/**
|
|
376
366
|
* Вбудовані та поширені **кластерні** `kind`, для яких **`metadata.namespace`** не застосовується.
|
|
377
367
|
* CRD з невідомим kind лишаються з вимогою namespace, якщо файл не в kustomization — за потреби додай path у `resources`.
|
|
@@ -887,104 +877,6 @@ async function validateKustomizationIncludesSvcHlWithSvc(root, yamlFiles, fail)
|
|
|
887
877
|
}
|
|
888
878
|
}
|
|
889
879
|
|
|
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
880
|
/**
|
|
989
881
|
* Шляхи лише з полів ресурсів Kustomization (**без** patch-файлів).
|
|
990
882
|
* @param {unknown} obj корінь першого документа Kustomization
|
|
@@ -1331,7 +1223,7 @@ async function kustomizationTreeHasDeploymentUnderK8sBase(kustAbs, rootNorm) {
|
|
|
1331
1223
|
|
|
1332
1224
|
/**
|
|
1333
1225
|
* Збирає дескриптори ресурсів з **`resources` / `bases` / `components` / `crds`** для одного дерева kustomization.
|
|
1334
|
-
* Повторний вхід у той самий **`kustomization.yaml`** дає порожній
|
|
1226
|
+
* Повторний вхід у той самий **`kustomization.yaml`** дає порожній внесок.
|
|
1335
1227
|
* @param {string} kustAbs абсолютний шлях до **kustomization.yaml**
|
|
1336
1228
|
* @param {string} rootNorm нормалізований абсолютний корінь репозиторію
|
|
1337
1229
|
* @param {Set<string>} visitedKustomization нормалізовані абсолютні шляхи відвіданих **kustomization.yaml**
|
|
@@ -3505,22 +3397,6 @@ async function validateHasuraHttpRouteCanon(root, yamlFiles, fail) {
|
|
|
3505
3397
|
}
|
|
3506
3398
|
}
|
|
3507
3399
|
|
|
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
3400
|
/**
|
|
3525
3401
|
* Вимагає непорожній **metadata.namespace** для namespaced-документів (крім кластерних kind).
|
|
3526
3402
|
* @param {unknown} manifest корінь YAML-документа
|
|
@@ -3574,7 +3450,7 @@ export function isK8sBaseManifestYamlPath(rel, baseLower) {
|
|
|
3574
3450
|
|
|
3575
3451
|
// Plan B: per-document валідаційне ядро для k8s YAML повністю в rego —
|
|
3576
3452
|
// `k8s.manifest`, `k8s.gateway`, `k8s.svc_yaml`, `k8s.svc_hl_yaml`,
|
|
3577
|
-
// `k8s.
|
|
3453
|
+
// `k8s.base_manifest`. Виклик через `runAllK8sRego`.
|
|
3578
3454
|
// JS-функції failIfK8sPolicyNamespaceRulesViolated, failIfK8sPolicyResourceRulesViolated,
|
|
3579
3455
|
// validateK8sYamlPolicyDocuments видалено.
|
|
3580
3456
|
|
|
@@ -3679,13 +3555,12 @@ function countSchemaModelines(lines) {
|
|
|
3679
3555
|
* @param {string[]} lines рядки файлу
|
|
3680
3556
|
* @param {(msg: string) => void} fail реєстрація помилки
|
|
3681
3557
|
* @param {(msg: string) => void} pass реєстрація успіху
|
|
3682
|
-
* @param {Set<string>} kustomizeManagedRel kustomize-managed шляхи
|
|
3683
3558
|
* @returns {void}
|
|
3684
3559
|
*/
|
|
3685
|
-
function checkK8sYamlHttpBackendGroupFile(rel, _baseLower, _lines, _fail, pass
|
|
3560
|
+
function checkK8sYamlHttpBackendGroupFile(rel, _baseLower, _lines, _fail, pass) {
|
|
3686
3561
|
// Per-document валідація (Ingress/autoscaling/v1 заборонено, Gateway API backendRef,
|
|
3687
|
-
// metadata.namespace правила) — у rego (`k8s.manifest`, `k8s.gateway`, `k8s.
|
|
3688
|
-
//
|
|
3562
|
+
// metadata.namespace правила) — у rego (`k8s.manifest`, `k8s.gateway`, `k8s.base_manifest`),
|
|
3563
|
+
// батч-виклик з `runAllK8sRego` на початку `check()`.
|
|
3689
3564
|
pass(`${rel}: HttpBackendGroup (alb.yc.io/v1alpha1) — modeline $schema не застосовується (k8s.mdc)`)
|
|
3690
3565
|
}
|
|
3691
3566
|
|
|
@@ -3697,10 +3572,9 @@ function checkK8sYamlHttpBackendGroupFile(rel, _baseLower, _lines, _fail, pass,
|
|
|
3697
3572
|
* @param {string[]} lines рядки файлу
|
|
3698
3573
|
* @param {(msg: string) => void} fail реєстрація помилки
|
|
3699
3574
|
* @param {(msg: string) => void} pass реєстрація успіху
|
|
3700
|
-
* @param {Set<string>} kustomizeManagedRel kustomize-managed шляхи
|
|
3701
3575
|
* @returns {void}
|
|
3702
3576
|
*/
|
|
3703
|
-
function checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pass
|
|
3577
|
+
function checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pass) {
|
|
3704
3578
|
const match = lines[0].match(MODELINE_RE)
|
|
3705
3579
|
if (!match) {
|
|
3706
3580
|
fail(`${rel}: некоректний modeline $schema у першому рядку`)
|
|
@@ -3746,10 +3620,9 @@ function checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pa
|
|
|
3746
3620
|
* @param {string} root корінь репозиторію
|
|
3747
3621
|
* @param {(msg: string) => void} fail реєстрація помилки
|
|
3748
3622
|
* @param {(msg: string) => void} pass реєстрація успіху
|
|
3749
|
-
* @param {Set<string>} kustomizeManagedRel відносні posix-шляхи з collectKustomizeManagedRelPaths
|
|
3750
3623
|
* @returns {Promise<void>}
|
|
3751
3624
|
*/
|
|
3752
|
-
async function checkK8sYamlFile(abs, root, fail, pass
|
|
3625
|
+
async function checkK8sYamlFile(abs, root, fail, pass) {
|
|
3753
3626
|
const rel = (relative(root, abs) || abs).replaceAll('\\', '/')
|
|
3754
3627
|
const base = basename(abs)
|
|
3755
3628
|
const baseLower = base.toLowerCase()
|
|
@@ -3790,7 +3663,7 @@ async function checkK8sYamlFile(abs, root, fail, pass, kustomizeManagedRel) {
|
|
|
3790
3663
|
)
|
|
3791
3664
|
return
|
|
3792
3665
|
}
|
|
3793
|
-
checkK8sYamlHttpBackendGroupFile(rel, baseLower, lines, fail, pass
|
|
3666
|
+
checkK8sYamlHttpBackendGroupFile(rel, baseLower, lines, fail, pass)
|
|
3794
3667
|
return
|
|
3795
3668
|
}
|
|
3796
3669
|
|
|
@@ -3799,7 +3672,7 @@ async function checkK8sYamlFile(abs, root, fail, pass, kustomizeManagedRel) {
|
|
|
3799
3672
|
return
|
|
3800
3673
|
}
|
|
3801
3674
|
|
|
3802
|
-
checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pass
|
|
3675
|
+
checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pass)
|
|
3803
3676
|
}
|
|
3804
3677
|
|
|
3805
3678
|
/**
|
|
@@ -6030,19 +5903,18 @@ async function runKustomizationImagesCleanup(kustAbs, rel, fail, pass) {
|
|
|
6030
5903
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
6031
5904
|
*/
|
|
6032
5905
|
/**
|
|
6033
|
-
* Plan B (rego-authoritative): на початку `check()` батч-викликаємо
|
|
6034
|
-
* rego
|
|
5906
|
+
* Plan B (rego-authoritative): на початку `check()` батч-викликаємо path-фільтровані
|
|
5907
|
+
* rego-пакети з `npm/policy/k8s/` через `runConftestBatch`. Пакети hasura_configmap і
|
|
6035
5908
|
* hasura_httproute мають cross-file gating (паруються з Hasura-Deployment) — вони запускаються
|
|
6036
5909
|
* з відповідних orchestrator-функцій (`validateHasuraConfigMapRemoteSchemaPermissions`,
|
|
6037
5910
|
* `validateHasuraHttpRouteCanon`). Структурна частина HPA/PDB (`k8s.hpa_pdb`) тут на всіх yaml,
|
|
6038
5911
|
* env-залежні межі min/maxReplicas і expected-name — JS-cross-file у `validateDeploymentHpaPdbAndTopology`.
|
|
6039
5912
|
* @param {string} root корінь репозиторію (cwd)
|
|
6040
5913
|
* @param {string[]} yamlFiles абсолютні шляхи знайдених *.yaml під `…/k8s/`
|
|
6041
|
-
* @param {Set<string>} kustomizeManagedRel відносні posix-шляхи kustomize-managed файлів
|
|
6042
5914
|
* @param {(msg: string) => void} fail callback при помилці
|
|
6043
5915
|
* @returns {void}
|
|
6044
5916
|
*/
|
|
6045
|
-
function runAllK8sRego(root, yamlFiles,
|
|
5917
|
+
function runAllK8sRego(root, yamlFiles, fail) {
|
|
6046
5918
|
const relOf = abs => relative(root, abs).replaceAll('\\', '/') || abs
|
|
6047
5919
|
|
|
6048
5920
|
const allYaml = yamlFiles
|
|
@@ -6055,7 +5927,6 @@ function runAllK8sRego(root, yamlFiles, kustomizeManagedRel, fail) {
|
|
|
6055
5927
|
if (!K8S_BASE_SEGMENT_RE.test(r)) return false
|
|
6056
5928
|
return basename(p).toLowerCase() !== 'kustomization.yaml'
|
|
6057
5929
|
})
|
|
6058
|
-
const kustomizeManagedFiles = yamlFiles.filter(p => kustomizeManagedRel.has(relOf(p)))
|
|
6059
5930
|
|
|
6060
5931
|
/** @type {Array<{ ns: string, dir: string, files: string[] }>} */
|
|
6061
5932
|
const targets = [
|
|
@@ -6066,8 +5937,7 @@ function runAllK8sRego(root, yamlFiles, kustomizeManagedRel, fail) {
|
|
|
6066
5937
|
{ ns: 'k8s.svc_yaml', dir: 'k8s/svc_yaml', files: svcYaml },
|
|
6067
5938
|
{ ns: 'k8s.svc_hl_yaml', dir: 'k8s/svc_hl_yaml', files: svcHlYaml },
|
|
6068
5939
|
{ 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 }
|
|
5940
|
+
{ ns: 'k8s.base_manifest', dir: 'k8s/base_manifest', files: baseResourceYaml }
|
|
6071
5941
|
]
|
|
6072
5942
|
|
|
6073
5943
|
for (const t of targets) {
|
|
@@ -6103,16 +5973,14 @@ export async function check() {
|
|
|
6103
5973
|
|
|
6104
5974
|
assertNoForbiddenK8sDevPaths(yamlFiles, root, fail)
|
|
6105
5975
|
|
|
6106
|
-
const kustomizeManagedRel = await collectKustomizeManagedRelPaths(root, yamlFiles)
|
|
6107
|
-
|
|
6108
5976
|
// Plan B: пер-документні структурні правила — у rego-полісі `npm/policy/k8s/*`,
|
|
6109
5977
|
// викликаємо одним батчем на namespace через runConftestBatch. JS нижче робить
|
|
6110
5978
|
// лише cross-file orchestration, modeline та FS-existence перевірки.
|
|
6111
|
-
runAllK8sRego(root, yamlFiles,
|
|
5979
|
+
runAllK8sRego(root, yamlFiles, fail)
|
|
6112
5980
|
pass(`Rego-полісі (npm/policy/k8s/*) виконано на ${yamlFiles.length} файл(ах)`)
|
|
6113
5981
|
|
|
6114
5982
|
for (const abs of yamlFiles) {
|
|
6115
|
-
await checkK8sYamlFile(abs, root, fail, pass
|
|
5983
|
+
await checkK8sYamlFile(abs, root, fail, pass)
|
|
6116
5984
|
}
|
|
6117
5985
|
|
|
6118
5986
|
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
|
-
}
|