@nitra/cursor 3.12.0 → 3.14.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/package.json +1 -1
- package/rules/k8s/js/manifests.mjs +145 -45
- package/rules/k8s/k8s.mdc +30 -1
- package/rules/k8s/policy/hasura_configmap/hasura_configmap.rego +83 -23
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.14.0] - 2026-06-02
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- k8s hasura_configmap: base/dev ConfigMap Hasura-Deployment має містити HASURA_GRAPHQL_ENABLED_APIS="metadata,graphql,pgdump" (точний рядок). Кожен не-base overlay (k8s/<env>/, env≠base/dev), що успадковує Hasura-base, має у kustomization.yaml перевизначати цей ключ до "metadata,graphql" (без pgdump) патчем JSON6902/Strategic Merge на ConfigMap — нова cross-file перевірка validateHasuraOverlayEnabledApisOverride
|
|
8
|
+
|
|
9
|
+
## [3.13.0] - 2026-06-02
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- k8s hasura_configmap: розширено перелік обов'язкових env у ConfigMap Hasura-Deployment — додатково до HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS="true" тепер вимагаються HASURA_GRAPHQL_ENABLE_RELAY="false", HASURA_GRAPHQL_ENABLE_TELEMETRY="false", HASURA_GRAPHQL_ENABLED_LOG_TYPES="startup,http-log" (точний рядок) і HASURA_GRAPHQL_DISABLE_EVENTING (ключ обов'язковий, значення довільне, за замовчуванням "true")
|
|
14
|
+
|
|
3
15
|
## [3.12.0] - 2026-06-02
|
|
4
16
|
|
|
5
17
|
### Added
|
package/package.json
CHANGED
|
@@ -2349,47 +2349,18 @@ export function isHasuraDeploymentManifest(manifest) {
|
|
|
2349
2349
|
}
|
|
2350
2350
|
|
|
2351
2351
|
/**
|
|
2352
|
-
* Обов'
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
if (v === true) return true
|
|
2365
|
-
if (typeof v === 'string' && v.trim().toLowerCase() === 'true') return true
|
|
2366
|
-
return false
|
|
2367
|
-
}
|
|
2368
|
-
|
|
2369
|
-
/**
|
|
2370
|
-
* Чи порушує ConfigMap вимогу щодо **`HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS: "true"`** (k8s.mdc).
|
|
2371
|
-
* Перевірка застосовна, коли в тому ж каталозі є Hasura-Deployment (див. `isHasuraDeploymentManifest`).
|
|
2372
|
-
* @param {unknown} manifest корінь YAML-документа ConfigMap
|
|
2373
|
-
* @returns {string | null} текст порушення або null, якщо не ConfigMap / ключ є і значення `true`
|
|
2374
|
-
*/
|
|
2375
|
-
export function hasuraConfigMapRemoteSchemaPermissionsViolation(manifest) {
|
|
2376
|
-
if (manifest === null || manifest === undefined || typeof manifest !== 'object' || Array.isArray(manifest))
|
|
2377
|
-
return null
|
|
2378
|
-
const rec = /** @type {Record<string, unknown>} */ (manifest)
|
|
2379
|
-
if (rec.kind !== 'ConfigMap') return null
|
|
2380
|
-
const data = rec.data
|
|
2381
|
-
if (data === null || data === undefined || typeof data !== 'object' || Array.isArray(data)) {
|
|
2382
|
-
return `data.${HASURA_REMOTE_SCHEMA_PERMISSIONS_KEY}: додай ключ зі значенням "true" (Deployment з hasura/graphql-engine — див. k8s.mdc)`
|
|
2383
|
-
}
|
|
2384
|
-
const d = /** @type {Record<string, unknown>} */ (data)
|
|
2385
|
-
if (!Object.hasOwn(d, HASURA_REMOTE_SCHEMA_PERMISSIONS_KEY)) {
|
|
2386
|
-
return `data.${HASURA_REMOTE_SCHEMA_PERMISSIONS_KEY}: додай ключ зі значенням "true" (Deployment з hasura/graphql-engine — див. k8s.mdc)`
|
|
2387
|
-
}
|
|
2388
|
-
if (!isConfigMapValueTrue(d[HASURA_REMOTE_SCHEMA_PERMISSIONS_KEY])) {
|
|
2389
|
-
return `data.${HASURA_REMOTE_SCHEMA_PERMISSIONS_KEY}: значення має бути "true" (зараз: ${JSON.stringify(d[HASURA_REMOTE_SCHEMA_PERMISSIONS_KEY])}) (див. k8s.mdc)`
|
|
2390
|
-
}
|
|
2391
|
-
return null
|
|
2392
|
-
}
|
|
2352
|
+
* Обов'язкові env-ключі у **`data`** ConfigMap для Hasura-Deployment (узгоджено з
|
|
2353
|
+
* rego-пакетом `k8s.hasura_configmap` та k8s.mdc). Лише для людиночитного pass-повідомлення —
|
|
2354
|
+
* авторитетна пер-документна валідація (наявність ключів і значення) живе в rego.
|
|
2355
|
+
*/
|
|
2356
|
+
export const HASURA_REQUIRED_ENV_KEYS = [
|
|
2357
|
+
'HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS',
|
|
2358
|
+
'HASURA_GRAPHQL_ENABLE_RELAY',
|
|
2359
|
+
'HASURA_GRAPHQL_ENABLE_TELEMETRY',
|
|
2360
|
+
'HASURA_GRAPHQL_ENABLED_LOG_TYPES',
|
|
2361
|
+
'HASURA_GRAPHQL_ENABLED_APIS',
|
|
2362
|
+
'HASURA_GRAPHQL_DISABLE_EVENTING'
|
|
2363
|
+
]
|
|
2393
2364
|
|
|
2394
2365
|
const K8S_YAML_EXT_RE = /\.ya?ml$/iu
|
|
2395
2366
|
|
|
@@ -3676,7 +3647,10 @@ async function validateConfigMapNameMatchesDeployment(root, yamlFilesAbs, fail,
|
|
|
3676
3647
|
|
|
3677
3648
|
/**
|
|
3678
3649
|
* Для кожного `k8s/base/configmap.yaml`, у каталозі якого поруч є Hasura-Deployment,
|
|
3679
|
-
* вимагає у `data`
|
|
3650
|
+
* вимагає у `data` обов'язкові env-ключі (`HASURA_REQUIRED_ENV_KEYS`) з очікуваними
|
|
3651
|
+
* значеннями (`HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS="true"`,
|
|
3652
|
+
* `HASURA_GRAPHQL_ENABLE_RELAY="false"`, `HASURA_GRAPHQL_ENABLE_TELEMETRY="false"`,
|
|
3653
|
+
* `HASURA_GRAPHQL_ENABLED_LOG_TYPES="startup,http-log"`, `HASURA_GRAPHQL_DISABLE_EVENTING` — будь-яке) (k8s.mdc).
|
|
3680
3654
|
* @param {string} root корінь репозиторію
|
|
3681
3655
|
* @param {string[]} yamlFilesAbs yaml під k8s
|
|
3682
3656
|
* @param {(msg: string) => void} fail callback при помилці
|
|
@@ -3688,8 +3662,8 @@ async function validateHasuraConfigMapRemoteSchemaPermissions(root, yamlFilesAbs
|
|
|
3688
3662
|
return CONFIGMAP_BASE_PATH_RE.test(`/${rel}`) || rel === 'k8s/base/configmap.yaml'
|
|
3689
3663
|
})
|
|
3690
3664
|
// JS gating: відберемо ConfigMap-файли, у каталозі яких поруч є Hasura-Deployment.
|
|
3691
|
-
// Per-document валідація `data.
|
|
3692
|
-
//
|
|
3665
|
+
// Per-document валідація обов'язкових `data.HASURA_GRAPHQL_*` env — у rego-пакеті
|
|
3666
|
+
// `k8s.hasura_configmap`.
|
|
3693
3667
|
const paired = []
|
|
3694
3668
|
for (const cmAbs of cmFiles) {
|
|
3695
3669
|
const deployment = await findDeploymentDocInDir(dirname(cmAbs))
|
|
@@ -3708,7 +3682,7 @@ async function validateHasuraConfigMapRemoteSchemaPermissions(root, yamlFilesAbs
|
|
|
3708
3682
|
fail(`${rel}: ${v.message}`)
|
|
3709
3683
|
}
|
|
3710
3684
|
if (violations.length === 0) {
|
|
3711
|
-
passFn(`Hasura-ConfigMap (${paired.length})
|
|
3685
|
+
passFn(`Hasura-ConfigMap (${paired.length}) містить обов'язкові env [${HASURA_REQUIRED_ENV_KEYS.join(', ')}] (rego)`)
|
|
3712
3686
|
}
|
|
3713
3687
|
}
|
|
3714
3688
|
|
|
@@ -4956,6 +4930,130 @@ async function validateProdKustomizationOverrides(root, yamlFilesAbs, fail, pass
|
|
|
4956
4930
|
}
|
|
4957
4931
|
}
|
|
4958
4932
|
|
|
4933
|
+
/** Очікуване `HASURA_GRAPHQL_ENABLED_APIS` у non-base/dev overlay (без `pgdump` — він лише для base/dev). */
|
|
4934
|
+
const HASURA_OVERLAY_ENABLED_APIS = 'metadata,graphql'
|
|
4935
|
+
|
|
4936
|
+
/** JSON-Pointer ключа `HASURA_GRAPHQL_ENABLED_APIS` у `data` ConfigMap (для JSON6902-патчів). */
|
|
4937
|
+
const HASURA_ENABLED_APIS_DATA_POINTER = '/data/HASURA_GRAPHQL_ENABLED_APIS'
|
|
4938
|
+
|
|
4939
|
+
/**
|
|
4940
|
+
* Чи дерево kustomization (`resources` / `bases` / `components` / `crds`, рекурсивно) містить
|
|
4941
|
+
* **Hasura-Deployment** у шарі base (образ `hasura/graphql-engine`). Маркер того, що overlay успадковує
|
|
4942
|
+
* Hasura-ConfigMap з pgdump-значенням `ENABLED_APIS` і має його перевизначити.
|
|
4943
|
+
* @param {string} kustAbs kustomization.yaml
|
|
4944
|
+
* @param {string} rootNorm нормалізований корінь репо
|
|
4945
|
+
* @returns {Promise<boolean>} true, якщо в успадкованому base є Hasura-Deployment
|
|
4946
|
+
*/
|
|
4947
|
+
export async function kustomizationTreeHasHasuraDeployment(kustAbs, rootNorm) {
|
|
4948
|
+
const visited = new Set()
|
|
4949
|
+
const paths = await collectYamlAbsPathsFromKustomizationTree(kustAbs, rootNorm, visited)
|
|
4950
|
+
const rootResolved = resolve(rootNorm)
|
|
4951
|
+
for (const abs of paths) {
|
|
4952
|
+
const rel = (relative(rootResolved, abs) || '').replaceAll('\\', '/')
|
|
4953
|
+
if (!isK8sYamlUnderBaseDirectory(rel)) continue
|
|
4954
|
+
const roots = await readK8sYamlDocumentRootsForInventory(abs)
|
|
4955
|
+
if (roots.some(o => isHasuraDeploymentManifest(o))) return true
|
|
4956
|
+
}
|
|
4957
|
+
return false
|
|
4958
|
+
}
|
|
4959
|
+
|
|
4960
|
+
/**
|
|
4961
|
+
* Значення, яке inline-`patch` присвоює `data.HASURA_GRAPHQL_ENABLED_APIS`. Підтримка двох форматів:
|
|
4962
|
+
* **JSON6902** (`op` add/replace на `/data/HASURA_GRAPHQL_ENABLED_APIS`) і **Strategic Merge**
|
|
4963
|
+
* (`data.HASURA_GRAPHQL_ENABLED_APIS`). Зовнішні patch-файли (`patches[].path`) не охоплені — Plan B trade-off.
|
|
4964
|
+
* @param {string} patchText вміст поля `patch`
|
|
4965
|
+
* @returns {string | null} присвоєне значення (рядок) або null, якщо patch не чіпає цей ключ
|
|
4966
|
+
*/
|
|
4967
|
+
export function enabledApisValueFromPatchText(patchText) {
|
|
4968
|
+
const t = typeof patchText === 'string' ? patchText.trim() : ''
|
|
4969
|
+
if (t === '') return null
|
|
4970
|
+
let parsed
|
|
4971
|
+
try {
|
|
4972
|
+
for (const d of parseAllDocuments(t)) {
|
|
4973
|
+
if (d.errors.length === 0) {
|
|
4974
|
+
parsed = d.toJSON()
|
|
4975
|
+
break
|
|
4976
|
+
}
|
|
4977
|
+
}
|
|
4978
|
+
} catch {
|
|
4979
|
+
return null
|
|
4980
|
+
}
|
|
4981
|
+
if (Array.isArray(parsed)) {
|
|
4982
|
+
for (const item of parsed) {
|
|
4983
|
+
if (item === null || typeof item !== 'object' || Array.isArray(item)) continue
|
|
4984
|
+
const rec = /** @type {Record<string, unknown>} */ (item)
|
|
4985
|
+
const op = typeof rec.op === 'string' ? rec.op.trim().toLowerCase() : ''
|
|
4986
|
+
const path = typeof rec.path === 'string' ? normalizeJsonPatchPath(rec.path) : ''
|
|
4987
|
+
if ((op === 'add' || op === 'replace') && path === HASURA_ENABLED_APIS_DATA_POINTER) {
|
|
4988
|
+
return typeof rec.value === 'string' ? rec.value : JSON.stringify(rec.value)
|
|
4989
|
+
}
|
|
4990
|
+
}
|
|
4991
|
+
return null
|
|
4992
|
+
}
|
|
4993
|
+
if (parsed === null || typeof parsed !== 'object') return null
|
|
4994
|
+
const data = /** @type {Record<string, unknown>} */ (parsed).data
|
|
4995
|
+
if (data === null || typeof data !== 'object' || Array.isArray(data)) return null
|
|
4996
|
+
const d = /** @type {Record<string, unknown>} */ (data)
|
|
4997
|
+
if (!Object.hasOwn(d, 'HASURA_GRAPHQL_ENABLED_APIS')) return null
|
|
4998
|
+
const v = d.HASURA_GRAPHQL_ENABLED_APIS
|
|
4999
|
+
return typeof v === 'string' ? v : JSON.stringify(v)
|
|
5000
|
+
}
|
|
5001
|
+
|
|
5002
|
+
/**
|
|
5003
|
+
* Значення, яке `patches[]` kustomization присвоюють `data.HASURA_GRAPHQL_ENABLED_APIS` на цілі **ConfigMap**.
|
|
5004
|
+
* Повертає значення першого patch-а, що чіпає цей ключ, або null, якщо такого немає.
|
|
5005
|
+
* @param {Record<string, unknown>} kust об'єкт kustomization.yaml
|
|
5006
|
+
* @returns {string | null} присвоєне значення або null
|
|
5007
|
+
*/
|
|
5008
|
+
export function hasuraEnabledApisOverrideValue(kust) {
|
|
5009
|
+
const patches = kust.patches
|
|
5010
|
+
if (!Array.isArray(patches)) return null
|
|
5011
|
+
for (const p of patches) {
|
|
5012
|
+
if (p === null || typeof p !== 'object' || Array.isArray(p)) continue
|
|
5013
|
+
const pr = /** @type {Record<string, unknown>} */ (p)
|
|
5014
|
+
if (typeof pr.patch !== 'string') continue
|
|
5015
|
+
if (resolvePatchTargetKind(pr) !== 'ConfigMap') continue
|
|
5016
|
+
const v = enabledApisValueFromPatchText(pr.patch)
|
|
5017
|
+
if (v !== null) return v
|
|
5018
|
+
}
|
|
5019
|
+
return null
|
|
5020
|
+
}
|
|
5021
|
+
|
|
5022
|
+
/**
|
|
5023
|
+
* Для кожного **non-base/dev** overlay `kustomization.yaml`, що успадковує Hasura-base (Deployment з
|
|
5024
|
+
* `hasura/graphql-engine`), вимагає у `patches[]` перевизначення **`data.HASURA_GRAPHQL_ENABLED_APIS`**
|
|
5025
|
+
* до **`"metadata,graphql"`** (pgdump лишається строго для base/dev) (k8s.mdc). `kind: Component`
|
|
5026
|
+
* пропускається (env-неутральне джерело, не overlay).
|
|
5027
|
+
* @param {string} root корінь репозиторію
|
|
5028
|
+
* @param {string[]} yamlFilesAbs yaml під k8s
|
|
5029
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
5030
|
+
* @param {(msg: string) => void} passFn callback при успіху
|
|
5031
|
+
*/
|
|
5032
|
+
async function validateHasuraOverlayEnabledApisOverride(root, yamlFilesAbs, fail, passFn) {
|
|
5033
|
+
const rootNorm = resolve(root)
|
|
5034
|
+
const kustFiles = yamlFilesAbs.filter(abs => basename(abs) === 'kustomization.yaml')
|
|
5035
|
+
for (const kustAbs of kustFiles) {
|
|
5036
|
+
const rel = (relative(rootNorm, kustAbs) || kustAbs).replaceAll('\\', '/')
|
|
5037
|
+
const segment = k8sEnvSegmentFromRelPath(rel)
|
|
5038
|
+
if (segment === null || segment === 'base' || segment === 'dev') continue
|
|
5039
|
+
const kust = await readFirstYamlObject(kustAbs)
|
|
5040
|
+
if (kust === null || kust.kind === 'Component') continue
|
|
5041
|
+
if (!(await kustomizationTreeHasHasuraDeployment(kustAbs, rootNorm))) continue
|
|
5042
|
+
const value = hasuraEnabledApisOverrideValue(kust)
|
|
5043
|
+
if (value === HASURA_OVERLAY_ENABLED_APIS) {
|
|
5044
|
+
passFn(`${rel}: overlay '${segment}' перевизначає HASURA_GRAPHQL_ENABLED_APIS="${HASURA_OVERLAY_ENABLED_APIS}" (k8s.mdc)`)
|
|
5045
|
+
} else if (value === null) {
|
|
5046
|
+
fail(
|
|
5047
|
+
`${rel}: overlay '${segment}' має у patches[] перевизначати data.HASURA_GRAPHQL_ENABLED_APIS до "${HASURA_OVERLAY_ENABLED_APIS}" (pgdump лише для base/dev) (k8s.mdc)`
|
|
5048
|
+
)
|
|
5049
|
+
} else {
|
|
5050
|
+
fail(
|
|
5051
|
+
`${rel}: overlay '${segment}' patch data.HASURA_GRAPHQL_ENABLED_APIS має бути "${HASURA_OVERLAY_ENABLED_APIS}" (зараз: ${JSON.stringify(value)}) (k8s.mdc)`
|
|
5052
|
+
)
|
|
5053
|
+
}
|
|
5054
|
+
}
|
|
5055
|
+
}
|
|
5056
|
+
|
|
4959
5057
|
/**
|
|
4960
5058
|
* Шукає HPA за `scaleTargetRef.name` серед документів.
|
|
4961
5059
|
* @param {Record<string, unknown>[]} hpaDocs масив HPA-документів
|
|
@@ -6653,5 +6751,7 @@ export async function check(cwd = process.cwd()) {
|
|
|
6653
6751
|
|
|
6654
6752
|
await validateProdKustomizationOverrides(root, yamlFiles, fail, pass)
|
|
6655
6753
|
|
|
6754
|
+
await validateHasuraOverlayEnabledApisOverride(root, yamlFiles, fail, pass)
|
|
6755
|
+
|
|
6656
6756
|
return reporter.getExitCode()
|
|
6657
6757
|
}
|
package/rules/k8s/k8s.mdc
CHANGED
|
@@ -297,11 +297,40 @@ spec:
|
|
|
297
297
|
|
|
298
298
|
## ConfigMap для Hasura-Deployment
|
|
299
299
|
|
|
300
|
-
Якщо в `k8s/base/` поруч із **`configmap.yaml`** є **Deployment** з образом **`hasura/graphql-engine`**, у `data` ConfigMap **обов'язково**
|
|
300
|
+
Якщо в `k8s/base/` поруч із **`configmap.yaml`** є **Deployment** з образом **`hasura/graphql-engine`**, у `data` ConfigMap **обов'язково** мають бути env-ключі:
|
|
301
|
+
|
|
302
|
+
- **`HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS`** зі значенням **`"true"`**;
|
|
303
|
+
- **`HASURA_GRAPHQL_ENABLE_RELAY`** зі значенням **`"false"`**;
|
|
304
|
+
- **`HASURA_GRAPHQL_ENABLE_TELEMETRY`** зі значенням **`"false"`**;
|
|
305
|
+
- **`HASURA_GRAPHQL_ENABLED_LOG_TYPES`** зі значенням **`"startup,http-log"`** (точний рядок);
|
|
306
|
+
- **`HASURA_GRAPHQL_ENABLED_APIS`** зі значенням **`"metadata,graphql,pgdump"`** (точний рядок) — **значення для base/dev**;
|
|
307
|
+
- **`HASURA_GRAPHQL_DISABLE_EVENTING`** — ключ обов'язковий, значення довільне (за замовчуванням **`"true"`**).
|
|
308
|
+
|
|
309
|
+
Точні умови перевірки — rego-пакет **`k8s.hasura_configmap`** (cross-file прив'язка ConfigMap↔Deployment — у `rules/k8s/js/manifests.mjs`).
|
|
301
310
|
|
|
302
311
|
```yaml
|
|
303
312
|
data:
|
|
304
313
|
HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS: 'true'
|
|
314
|
+
HASURA_GRAPHQL_ENABLE_RELAY: 'false'
|
|
315
|
+
HASURA_GRAPHQL_ENABLE_TELEMETRY: 'false'
|
|
316
|
+
HASURA_GRAPHQL_ENABLED_LOG_TYPES: 'startup,http-log'
|
|
317
|
+
HASURA_GRAPHQL_ENABLED_APIS: 'metadata,graphql,pgdump'
|
|
318
|
+
HASURA_GRAPHQL_DISABLE_EVENTING: 'true'
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### `HASURA_GRAPHQL_ENABLED_APIS` поза base/dev
|
|
322
|
+
|
|
323
|
+
`pgdump` дозволено **лише** для **base**/**dev**. Кожен **не-base** overlay (`k8s/<env>/`, де `<env>` ≠ `base`/`dev`), що успадковує Hasura-base, **зобов'язаний** у своєму **`kustomization.yaml`** перевизначити `HASURA_GRAPHQL_ENABLED_APIS` до **`"metadata,graphql"`** (без `pgdump`) — патчем JSON6902 або Strategic Merge на ціль **ConfigMap**. Перевірка — cross-file у `rules/k8s/js/manifests.mjs` (`validateHasuraOverlayEnabledApisOverride`); `kind: Component` пропускається.
|
|
324
|
+
|
|
325
|
+
```yaml title="k8s/prod/kustomization.yaml"
|
|
326
|
+
patches:
|
|
327
|
+
- target:
|
|
328
|
+
kind: ConfigMap
|
|
329
|
+
name: db-h
|
|
330
|
+
patch: |
|
|
331
|
+
- op: replace
|
|
332
|
+
path: /data/HASURA_GRAPHQL_ENABLED_APIS
|
|
333
|
+
value: metadata,graphql
|
|
305
334
|
```
|
|
306
335
|
|
|
307
336
|
## Kustomize: структура каталогів (`base` / overlays)
|
|
@@ -1,18 +1,33 @@
|
|
|
1
1
|
# Порт перевірки ConfigMap для Hasura-Deployment з
|
|
2
2
|
# `npm/scripts/rules/k8s/fix.mjs` (k8s.mdc): у ConfigMap, що сусідствує з
|
|
3
|
-
# Hasura-Deployment, у `data` обов'язково
|
|
4
|
-
# `
|
|
3
|
+
# Hasura-Deployment, у `data` обов'язково мають бути env-ключі зі списку
|
|
4
|
+
# `required_env` з очікуваними значеннями:
|
|
5
|
+
# "HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS" → "true"
|
|
6
|
+
# "HASURA_GRAPHQL_ENABLE_RELAY" → "false"
|
|
7
|
+
# "HASURA_GRAPHQL_ENABLE_TELEMETRY" → "false"
|
|
8
|
+
# "HASURA_GRAPHQL_ENABLED_LOG_TYPES" → "startup,http-log" (точний рядок)
|
|
9
|
+
# "HASURA_GRAPHQL_ENABLED_APIS" → "metadata,graphql,pgdump" (точний рядок;
|
|
10
|
+
# це значення для base/dev. Не-base overlay-и мають зводити його до "metadata,graphql"
|
|
11
|
+
# патчем у kustomization.yaml — перевіряє JS `validateHasuraOverlayEnabledApisOverride`)
|
|
12
|
+
# "HASURA_GRAPHQL_DISABLE_EVENTING" → null (ключ обов'язковий,
|
|
13
|
+
# значення довільне; за замовчуванням рекомендовано "true")
|
|
14
|
+
#
|
|
15
|
+
# Семантика очікуваного значення у `required_env`:
|
|
16
|
+
# "true" — має читатись як логічне true (boolean true або рядок "true", case-insensitive);
|
|
17
|
+
# "false" — має читатись як логічне false (boolean false або рядок "false", case-insensitive);
|
|
18
|
+
# null — ключ обов'язковий, значення довільне (за замовчуванням "true");
|
|
19
|
+
# інший рядок — значення має точно дорівнювати рядку (exact match).
|
|
5
20
|
#
|
|
6
21
|
# Запуск (локально, лише для ConfigMap у каталозі з Hasura-Deployment):
|
|
7
22
|
# conftest test path/to/k8s/.../configmap.yaml \
|
|
8
23
|
# -p npm/policy/k8s/hasura_configmap \
|
|
9
24
|
# --namespace k8s.hasura_configmap
|
|
10
25
|
#
|
|
11
|
-
# Прив'язка ConfigMap-Deployment cross-file — у JS (`rules/k8s/
|
|
26
|
+
# Прив'язка ConfigMap-Deployment cross-file — у JS (`rules/k8s/js/manifests.mjs`:
|
|
12
27
|
# `validateHasuraConfigMapRemoteSchemaPermissions` шукає Hasura-Deployment
|
|
13
28
|
# у тому ж dir-у і викликає conftest з цією намеспейс лише для відповідних
|
|
14
|
-
# ConfigMap-ів).
|
|
15
|
-
#
|
|
29
|
+
# ConfigMap-ів). Rego authoritative для пер-документної валідації; JS лишає
|
|
30
|
+
# лише cross-file orchestration.
|
|
16
31
|
#
|
|
17
32
|
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
18
33
|
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`.
|
|
@@ -20,45 +35,83 @@ package k8s.hasura_configmap
|
|
|
20
35
|
|
|
21
36
|
import rego.v1
|
|
22
37
|
|
|
23
|
-
# Обов'
|
|
24
|
-
|
|
38
|
+
# Обов'язкові env-ключі у `data` (узгоджено з `rules/k8s/js/manifests.mjs` та k8s.mdc).
|
|
39
|
+
# Значення — очікуваний стан ключа (семантика — у шапці файлу).
|
|
40
|
+
required_env := {
|
|
41
|
+
"HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS": "true",
|
|
42
|
+
"HASURA_GRAPHQL_ENABLE_RELAY": "false",
|
|
43
|
+
"HASURA_GRAPHQL_ENABLE_TELEMETRY": "false",
|
|
44
|
+
"HASURA_GRAPHQL_ENABLED_LOG_TYPES": "startup,http-log",
|
|
45
|
+
"HASURA_GRAPHQL_ENABLED_APIS": "metadata,graphql,pgdump",
|
|
46
|
+
"HASURA_GRAPHQL_DISABLE_EVENTING": null,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# Множина "boolean-подібних" очікувань — для них значення читається як логічне,
|
|
50
|
+
# а не звіряється точним рядком.
|
|
51
|
+
bool_expected := {"true", "false"}
|
|
52
|
+
|
|
53
|
+
# Підказка про очікуване значення для повідомлення про відсутній ключ.
|
|
54
|
+
expected_hint(null) := "(значення довільне, за замовчуванням \"true\")"
|
|
25
55
|
|
|
26
|
-
|
|
27
|
-
"data.%s: додай ключ зі значенням \"true\"",
|
|
28
|
-
"(Deployment з hasura/graphql-engine — k8s.mdc)",
|
|
29
|
-
])
|
|
56
|
+
expected_hint(expected) := sprintf("зі значенням \"%s\"", [expected]) if is_string(expected)
|
|
30
57
|
|
|
31
|
-
key_value_wrong_template := concat(" ", ["data.%s: значення має бути \"
|
|
58
|
+
key_value_wrong_template := concat(" ", ["data.%s: значення має бути \"%s\" (зараз: %v) (k8s.mdc)"])
|
|
59
|
+
|
|
60
|
+
# Ключ відсутній: `data` не об'єкт або в ньому немає обов'язкового ключа.
|
|
61
|
+
deny contains msg if {
|
|
62
|
+
input.kind == "ConfigMap"
|
|
63
|
+
some key, expected in required_env
|
|
64
|
+
not key_present(key)
|
|
65
|
+
msg := sprintf(
|
|
66
|
+
"data.%s: додай ключ %s (Deployment з hasura/graphql-engine — k8s.mdc)",
|
|
67
|
+
[key, expected_hint(expected)],
|
|
68
|
+
)
|
|
69
|
+
}
|
|
32
70
|
|
|
71
|
+
# Очікуване "true", а значення не читається як логічне true.
|
|
33
72
|
deny contains msg if {
|
|
34
73
|
input.kind == "ConfigMap"
|
|
35
|
-
|
|
36
|
-
|
|
74
|
+
d := object.get(input, "data", null)
|
|
75
|
+
is_object(d)
|
|
76
|
+
some key, expected in required_env
|
|
77
|
+
expected == "true"
|
|
78
|
+
key in object.keys(d)
|
|
79
|
+
not is_value_true(d[key])
|
|
80
|
+
msg := sprintf(key_value_wrong_template, [key, "true", d[key]])
|
|
37
81
|
}
|
|
38
82
|
|
|
83
|
+
# Очікуване "false", а значення не читається як логічне false.
|
|
39
84
|
deny contains msg if {
|
|
40
85
|
input.kind == "ConfigMap"
|
|
41
86
|
d := object.get(input, "data", null)
|
|
42
87
|
is_object(d)
|
|
43
|
-
|
|
44
|
-
|
|
88
|
+
some key, expected in required_env
|
|
89
|
+
expected == "false"
|
|
90
|
+
key in object.keys(d)
|
|
91
|
+
not is_value_false(d[key])
|
|
92
|
+
msg := sprintf(key_value_wrong_template, [key, "false", d[key]])
|
|
45
93
|
}
|
|
46
94
|
|
|
95
|
+
# Очікуване — точний рядок (не "true"/"false"/null), а значення не збігається.
|
|
47
96
|
deny contains msg if {
|
|
48
97
|
input.kind == "ConfigMap"
|
|
49
98
|
d := object.get(input, "data", null)
|
|
50
99
|
is_object(d)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
not
|
|
54
|
-
|
|
100
|
+
some key, expected in required_env
|
|
101
|
+
is_string(expected)
|
|
102
|
+
not expected in bool_expected
|
|
103
|
+
key in object.keys(d)
|
|
104
|
+
d[key] != expected
|
|
105
|
+
msg := sprintf(key_value_wrong_template, [key, expected, d[key]])
|
|
55
106
|
}
|
|
56
107
|
|
|
57
|
-
key_present(
|
|
58
|
-
|
|
108
|
+
key_present(key) if {
|
|
109
|
+
d := object.get(input, "data", null)
|
|
110
|
+
is_object(d)
|
|
111
|
+
key in object.keys(d)
|
|
59
112
|
}
|
|
60
113
|
|
|
61
|
-
# Значення вважається "true", якщо це boolean
|
|
114
|
+
# Значення вважається "true"/"false", якщо це відповідний boolean або рядок
|
|
62
115
|
# (case-insensitive). ConfigMap у Kubernetes тримає рядки, але YAML без лапок
|
|
63
116
|
# дає boolean — приймаємо обидва варіанти.
|
|
64
117
|
is_value_true(true)
|
|
@@ -67,3 +120,10 @@ is_value_true(v) if {
|
|
|
67
120
|
is_string(v)
|
|
68
121
|
lower(trim_space(v)) == "true"
|
|
69
122
|
}
|
|
123
|
+
|
|
124
|
+
is_value_false(false)
|
|
125
|
+
|
|
126
|
+
is_value_false(v) if {
|
|
127
|
+
is_string(v)
|
|
128
|
+
lower(trim_space(v)) == "false"
|
|
129
|
+
}
|