@nitra/cursor 1.8.222 → 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 +66 -0
- package/bin/n-cursor.js +3 -2
- package/mdc/abie.mdc +13 -0
- package/mdc/changelog.mdc +3 -2
- package/mdc/ci4.mdc +8 -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/abie/base_deployment_preem/base_deployment_preem.rego +56 -0
- package/policy/abie/base_deployment_preem/base_deployment_preem_test.rego +60 -0
- package/policy/abie/clean_merged_ignore_branches/clean_merged_ignore_branches.rego +100 -0
- package/policy/abie/clean_merged_ignore_branches/clean_merged_ignore_branches_test.rego +48 -0
- package/policy/abie/health_check_policy/health_check_policy.rego +91 -22
- package/policy/abie/health_check_policy/health_check_policy_test.rego +99 -0
- package/policy/abie/http_route_base/http_route_base_test.rego +64 -0
- package/policy/k8s/kustomization/kustomization.rego +2 -2
- package/policy/k8s/manifest/manifest.rego +4 -2
- package/scripts/check-abie.mjs +102 -369
- package/scripts/check-ga.mjs +89 -9
- package/scripts/check-k8s.mjs +129 -704
- package/scripts/lint-conftest.mjs +25 -2
- package/scripts/lint-ga.mjs +18 -132
- package/scripts/utils/run-conftest-batch.mjs +117 -0
- package/policy/k8s/kustomize_managed/kustomize_managed.rego +0 -31
- package/policy/k8s/kustomize_managed/kustomize_managed_test.rego +0 -30
package/scripts/check-k8s.mjs
CHANGED
|
@@ -131,6 +131,7 @@ import { isSeq, parseAllDocuments, parseDocument } from 'yaml'
|
|
|
131
131
|
|
|
132
132
|
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
133
133
|
import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
|
|
134
|
+
import { runConftestBatch } from './utils/run-conftest-batch.mjs'
|
|
134
135
|
import { walkDir } from './utils/walkDir.mjs'
|
|
135
136
|
|
|
136
137
|
/** Версія набору схем yannh — узгоджено з k8s.mdc */
|
|
@@ -359,18 +360,6 @@ export function isForbiddenK8sDevPath(rel) {
|
|
|
359
360
|
return n.includes('/k8s/dev/')
|
|
360
361
|
}
|
|
361
362
|
|
|
362
|
-
/**
|
|
363
|
-
* Відносний шлях від кореня репозиторію у вигляді з `/` (для множини kustomize).
|
|
364
|
-
* @param {string} root корінь cwd
|
|
365
|
-
* @param {string} abs абсолютний шлях
|
|
366
|
-
* @returns {string | null} posix-відносний шлях або null, якщо поза root
|
|
367
|
-
*/
|
|
368
|
-
function posixRelFromAbs(root, abs) {
|
|
369
|
-
const r = (relative(root, abs) || abs).replaceAll('\\', '/')
|
|
370
|
-
if (r.startsWith('..')) return null
|
|
371
|
-
return r
|
|
372
|
-
}
|
|
373
|
-
|
|
374
363
|
/**
|
|
375
364
|
* Вбудовані та поширені **кластерні** `kind`, для яких **`metadata.namespace`** не застосовується.
|
|
376
365
|
* CRD з невідомим kind лишаються з вимогою namespace, якщо файл не в kustomization — за потреби додай path у `resources`.
|
|
@@ -477,25 +466,9 @@ export function kustomizationResourcesSortedAlphabeticallyViolation(obj) {
|
|
|
477
466
|
return null
|
|
478
467
|
}
|
|
479
468
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
* @param {string[]} yamlFilesAbs yaml під k8s
|
|
484
|
-
* @param {(msg: string) => void} fail функція для фіксації порушення
|
|
485
|
-
* @returns {Promise<void>} завершується після перевірки всіх kustomization.yaml
|
|
486
|
-
*/
|
|
487
|
-
async function validateKustomizationResourcesSortedAlphabetically(root, yamlFilesAbs, fail) {
|
|
488
|
-
for (const kustAbs of yamlFilesAbs.filter(p => basename(p).toLowerCase() === 'kustomization.yaml')) {
|
|
489
|
-
const rel = (relative(root, kustAbs) || kustAbs).replaceAll('\\', '/')
|
|
490
|
-
const kust = await readFirstYamlObject(kustAbs)
|
|
491
|
-
if (kust !== null) {
|
|
492
|
-
const v = kustomizationResourcesSortedAlphabeticallyViolation(kust)
|
|
493
|
-
if (v !== null) {
|
|
494
|
-
fail(`${rel}: ${v}`)
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
469
|
+
// Plan B: per-document `resources[]` sort у Kustomization — у rego-пакеті
|
|
470
|
+
// `k8s.kustomization`, викликається з `runAllK8sRego` на початку `check()`.
|
|
471
|
+
// JS-orchestrator validateKustomizationResourcesSortedAlphabetically видалено.
|
|
499
472
|
|
|
500
473
|
/**
|
|
501
474
|
* Лексичне порівняння двох tuple рядків через `localeCompare('en', { sensitivity: 'base' })`.
|
|
@@ -662,42 +635,9 @@ export function kustomizationInlinePatchOpsSortedViolation(patchText) {
|
|
|
662
635
|
return `inline patch (JSON6902) має бути за алфавітом по path. Зараз: ${paths.join(', ')}; очікувано: ${want.join(', ')} (k8s.mdc)`
|
|
663
636
|
}
|
|
664
637
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
* @param {string} root корінь репо
|
|
669
|
-
* @param {string[]} yamlFilesAbs yaml під k8s
|
|
670
|
-
* @param {(msg: string) => void} fail функція для фіксації порушення
|
|
671
|
-
* @returns {Promise<void>} завершується після перевірки всіх kustomization.yaml
|
|
672
|
-
*/
|
|
673
|
-
async function validateKustomizationPatchesStructuralSort(root, yamlFilesAbs, fail) {
|
|
674
|
-
for (const kustAbs of yamlFilesAbs.filter(p => basename(p).toLowerCase() === 'kustomization.yaml')) {
|
|
675
|
-
const rel = (relative(root, kustAbs) || kustAbs).replaceAll('\\', '/')
|
|
676
|
-
const kust = await readFirstYamlObject(kustAbs)
|
|
677
|
-
if (kust === null) continue
|
|
678
|
-
const outer = kustomizationPatchesSortedViolation(kust)
|
|
679
|
-
if (outer !== null) fail(`${rel}: ${outer}`)
|
|
680
|
-
if (!Array.isArray(kust.patches)) continue
|
|
681
|
-
validateInlinePatchesSorted(rel, kust.patches, fail)
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
/**
|
|
686
|
-
* Перевіряє, що inline-`patch:` (рядок YAML/JSON) у кожному `patches[i]` має ops у канонічному порядку
|
|
687
|
-
* (`add`/`replace` за `path`). Чужі форми (без `patch`-стрічки, з `target` без inline-блока) пропускаються.
|
|
688
|
-
* @param {string} rel відносний шлях `kustomization.yaml` для повідомлень
|
|
689
|
-
* @param {unknown[]} patches масив `kust.patches` (рекордів)
|
|
690
|
-
* @param {(msg: string) => void} fail callback при порушенні
|
|
691
|
-
*/
|
|
692
|
-
function validateInlinePatchesSorted(rel, patches, fail) {
|
|
693
|
-
for (const [i, p] of patches.entries()) {
|
|
694
|
-
if (p === null || typeof p !== 'object' || Array.isArray(p)) continue
|
|
695
|
-
const rec = /** @type {Record<string, unknown>} */ (p)
|
|
696
|
-
if (typeof rec.patch !== 'string') continue
|
|
697
|
-
const v = kustomizationInlinePatchOpsSortedViolation(rec.patch)
|
|
698
|
-
if (v !== null) fail(`${rel}: patches[${i}] (${kustomizationPatchLabel(p, i)}): ${v}`)
|
|
699
|
-
}
|
|
700
|
-
}
|
|
638
|
+
// Plan B: validateKustomizationPatchesStructuralSort видалено. Per-document
|
|
639
|
+
// `patches[]` sort + inline JSON6902 ops sort — у rego-пакеті `k8s.kustomization`,
|
|
640
|
+
// викликається з `runAllK8sRego`.
|
|
701
641
|
|
|
702
642
|
/**
|
|
703
643
|
* Шляхи з полів Kustomization для resolve відносно каталогу **`kustomization.yaml`**.
|
|
@@ -935,104 +875,6 @@ async function validateKustomizationIncludesSvcHlWithSvc(root, yamlFiles, fail)
|
|
|
935
875
|
}
|
|
936
876
|
}
|
|
937
877
|
|
|
938
|
-
/**
|
|
939
|
-
* Збирає відносні шляхи (posix) до YAML, підключених до Kustomize з будь-якого **`kustomization.yaml`** під `k8s`.
|
|
940
|
-
* Обходить **`resources`**, **`bases`**, **`components`**, **`crds`**, **`patches[].path`**, **`patchesStrategicMerge`**;
|
|
941
|
-
* для каталогу з **`kustomization.yaml`** виконує рекурсивний обхід.
|
|
942
|
-
* @param {string} root корінь репозиторію
|
|
943
|
-
* @param {string[]} yamlFilesAbs відсортовані абсолютні шляхи до `*.yaml` / `*.yml` під k8s (для `.yml` check-k8s вимагає перейменувати на `.yaml`)
|
|
944
|
-
* @returns {Promise<Set<string>>} множина відносних шляхів до керованих файлів
|
|
945
|
-
*/
|
|
946
|
-
export async function collectKustomizeManagedRelPaths(root, yamlFilesAbs) {
|
|
947
|
-
/** @type {Set<string>} */
|
|
948
|
-
const managed = new Set()
|
|
949
|
-
const kustomizationAbsList = yamlFilesAbs.filter(abs => {
|
|
950
|
-
const b = basename(abs).toLowerCase()
|
|
951
|
-
return b === 'kustomization.yaml'
|
|
952
|
-
})
|
|
953
|
-
|
|
954
|
-
/** @type {Set<string>} */
|
|
955
|
-
const visitedKustomization = new Set()
|
|
956
|
-
|
|
957
|
-
/**
|
|
958
|
-
* @param {string} kustAbs абсолютний шлях до kustomization.yaml
|
|
959
|
-
* @returns {Promise<void>}
|
|
960
|
-
*/
|
|
961
|
-
async function walkKustomization(kustAbs) {
|
|
962
|
-
const normKust = resolve(kustAbs)
|
|
963
|
-
if (visitedKustomization.has(normKust)) return
|
|
964
|
-
visitedKustomization.add(normKust)
|
|
965
|
-
|
|
966
|
-
let raw
|
|
967
|
-
try {
|
|
968
|
-
raw = await readFile(normKust, 'utf8')
|
|
969
|
-
} catch {
|
|
970
|
-
return
|
|
971
|
-
}
|
|
972
|
-
const lines = toLines(raw)
|
|
973
|
-
const body = yamlBodyAfterModeline(lines)
|
|
974
|
-
|
|
975
|
-
/** @type {import('yaml').Document[] | undefined} */
|
|
976
|
-
let docs
|
|
977
|
-
try {
|
|
978
|
-
docs = parseAllDocuments(body)
|
|
979
|
-
} catch {
|
|
980
|
-
return
|
|
981
|
-
}
|
|
982
|
-
const first = docs[0]?.toJSON()
|
|
983
|
-
if (first === null || first === undefined || typeof first !== 'object' || Array.isArray(first)) return
|
|
984
|
-
|
|
985
|
-
const kustDir = dirname(normKust)
|
|
986
|
-
const pathRefs = pathsFromKustomizationObject(first)
|
|
987
|
-
|
|
988
|
-
/**
|
|
989
|
-
* @param {string} ref шлях з kustomization
|
|
990
|
-
* @returns {Promise<void>}
|
|
991
|
-
*/
|
|
992
|
-
async function handleKustomizeManagedPathRef(ref) {
|
|
993
|
-
if (ref.includes('://')) {
|
|
994
|
-
return
|
|
995
|
-
}
|
|
996
|
-
const resolved = resolve(kustDir, ref)
|
|
997
|
-
let st
|
|
998
|
-
try {
|
|
999
|
-
st = await stat(resolved)
|
|
1000
|
-
} catch {
|
|
1001
|
-
st = undefined
|
|
1002
|
-
}
|
|
1003
|
-
if (!st) {
|
|
1004
|
-
return
|
|
1005
|
-
}
|
|
1006
|
-
if (st.isFile()) {
|
|
1007
|
-
if (YAML_EXTENSION_RE.test(resolved)) {
|
|
1008
|
-
const pr = posixRelFromAbs(root, resolved)
|
|
1009
|
-
if (pr !== null) {
|
|
1010
|
-
managed.add(pr)
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
return
|
|
1014
|
-
}
|
|
1015
|
-
if (!st.isDirectory()) {
|
|
1016
|
-
return
|
|
1017
|
-
}
|
|
1018
|
-
const childK = existsSync(join(resolved, 'kustomization.yaml')) ? join(resolved, 'kustomization.yaml') : null
|
|
1019
|
-
if (childK !== null) {
|
|
1020
|
-
await walkKustomization(childK)
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
for (const ref of pathRefs) {
|
|
1025
|
-
await handleKustomizeManagedPathRef(ref)
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
for (const k of kustomizationAbsList) {
|
|
1030
|
-
await walkKustomization(k)
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
return managed
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
878
|
/**
|
|
1037
879
|
* Шляхи лише з полів ресурсів Kustomization (**без** patch-файлів).
|
|
1038
880
|
* @param {unknown} obj корінь першого документа Kustomization
|
|
@@ -1379,7 +1221,7 @@ async function kustomizationTreeHasDeploymentUnderK8sBase(kustAbs, rootNorm) {
|
|
|
1379
1221
|
|
|
1380
1222
|
/**
|
|
1381
1223
|
* Збирає дескриптори ресурсів з **`resources` / `bases` / `components` / `crds`** для одного дерева kustomization.
|
|
1382
|
-
* Повторний вхід у той самий **`kustomization.yaml`** дає порожній
|
|
1224
|
+
* Повторний вхід у той самий **`kustomization.yaml`** дає порожній внесок.
|
|
1383
1225
|
* @param {string} kustAbs абсолютний шлях до **kustomization.yaml**
|
|
1384
1226
|
* @param {string} rootNorm нормалізований абсолютний корінь репозиторію
|
|
1385
1227
|
* @param {Set<string>} visitedKustomization нормалізовані абсолютні шляхи відвіданих **kustomization.yaml**
|
|
@@ -2285,111 +2127,11 @@ export function json6902PathsWithRemoveAndAddOnSamePath(ops) {
|
|
|
2285
2127
|
return out.toSorted((a, b) => a.localeCompare(b))
|
|
2286
2128
|
}
|
|
2287
2129
|
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
* @param {(msg: string) => void} fail реєстрація порушення
|
|
2294
|
-
* @returns {void}
|
|
2295
|
-
*/
|
|
2296
|
-
function failIfJson6902RemoveAddConflictOnSamePath(rel, label, patchText, fail) {
|
|
2297
|
-
const ops = collectJson6902OperationsFromPatchText(patchText)
|
|
2298
|
-
const bad = json6902PathsWithRemoveAndAddOnSamePath(ops)
|
|
2299
|
-
if (bad.length > 0) {
|
|
2300
|
-
fail(`${rel}: ${label}: один path має і remove, і add — оформи як op: replace (k8s.mdc): ${bad.join(', ')}`)
|
|
2301
|
-
}
|
|
2302
|
-
}
|
|
2303
|
-
|
|
2304
|
-
/**
|
|
2305
|
-
* Зовнішній patch-файл (масив JSON6902): remove+add на один path.
|
|
2306
|
-
* @param {string} rel відносний шлях до kustomization.yaml
|
|
2307
|
-
* @param {string} resolved абсолютний шлях до файлу patch
|
|
2308
|
-
* @param {string} root корінь репо
|
|
2309
|
-
* @param {string} patchRef відносне посилання з kustomization
|
|
2310
|
-
* @param {(msg: string) => void} fail реєстрація порушення
|
|
2311
|
-
* @returns {Promise<void>}
|
|
2312
|
-
*/
|
|
2313
|
-
async function auditJson6902PatchExternalFile(rel, resolved, root, patchRef, fail) {
|
|
2314
|
-
/** @type {import('node:fs').Stats | null} */
|
|
2315
|
-
let st
|
|
2316
|
-
try {
|
|
2317
|
-
st = await stat(resolved)
|
|
2318
|
-
} catch {
|
|
2319
|
-
st = null
|
|
2320
|
-
}
|
|
2321
|
-
if (st === null || !st.isFile()) {
|
|
2322
|
-
return
|
|
2323
|
-
}
|
|
2324
|
-
let pRaw
|
|
2325
|
-
try {
|
|
2326
|
-
pRaw = await readFile(resolved, 'utf8')
|
|
2327
|
-
} catch {
|
|
2328
|
-
return
|
|
2329
|
-
}
|
|
2330
|
-
const ops = collectJson6902OperationsFromPatchText(pRaw)
|
|
2331
|
-
if (ops.length === 0) {
|
|
2332
|
-
return
|
|
2333
|
-
}
|
|
2334
|
-
const bad = json6902PathsWithRemoveAndAddOnSamePath(ops)
|
|
2335
|
-
if (bad.length === 0) {
|
|
2336
|
-
return
|
|
2337
|
-
}
|
|
2338
|
-
const relPatch = (relative(root, resolved) || patchRef).replaceAll('\\', '/')
|
|
2339
|
-
fail(
|
|
2340
|
-
`${rel}: patch-файл «${relPatch}»: один path має і remove, і add — оформи як op: replace (k8s.mdc): ${bad.join(', ')}`
|
|
2341
|
-
)
|
|
2342
|
-
}
|
|
2343
|
-
|
|
2344
|
-
/**
|
|
2345
|
-
* Один елемент **`patches[]`**: inline JSON6902 або зовнішній patch-файл.
|
|
2346
|
-
* @param {string} rel відносний шлях до kustomization.yaml
|
|
2347
|
-
* @param {Record<string, unknown>} pr об’єкт patch
|
|
2348
|
-
* @param {number} patchIdx 1-based індекс у масиві
|
|
2349
|
-
* @param {string} kustAbs абсолютний шлях до kustomization.yaml
|
|
2350
|
-
* @param {string} rootNorm нормалізований корінь репо
|
|
2351
|
-
* @param {string} root корінь репо
|
|
2352
|
-
* @param {(msg: string) => void} fail реєстрація порушення
|
|
2353
|
-
* @returns {Promise<void>}
|
|
2354
|
-
*/
|
|
2355
|
-
async function auditOneKustomizationJson6902Patch(rel, pr, patchIdx, kustAbs, rootNorm, root, fail) {
|
|
2356
|
-
if (typeof pr.patch === 'string' && pr.patch.trim() !== '') {
|
|
2357
|
-
failIfJson6902RemoveAddConflictOnSamePath(rel, `patches[${patchIdx}] inline JSON6902`, pr.patch, fail)
|
|
2358
|
-
}
|
|
2359
|
-
if (typeof pr.path !== 'string' || pr.path.trim() === '') {
|
|
2360
|
-
return
|
|
2361
|
-
}
|
|
2362
|
-
const patchRef = pr.path.trim()
|
|
2363
|
-
const resolved = resolve(dirname(kustAbs), patchRef)
|
|
2364
|
-
if (!resolvedFilePathIsUnderRoot(rootNorm, resolved) || !existsSync(resolved)) {
|
|
2365
|
-
return
|
|
2366
|
-
}
|
|
2367
|
-
await auditJson6902PatchExternalFile(rel, resolved, root, patchRef, fail)
|
|
2368
|
-
}
|
|
2369
|
-
|
|
2370
|
-
/**
|
|
2371
|
-
* Усі **`patches[]`** у Kustomization: inline та зовнішні файли.
|
|
2372
|
-
* @param {string} rel відносний шлях до kustomization.yaml
|
|
2373
|
-
* @param {unknown} patches поле **patches**
|
|
2374
|
-
* @param {string} kustAbs абсолютний шлях до kustomization.yaml
|
|
2375
|
-
* @param {string} rootNorm нормалізований корінь репо
|
|
2376
|
-
* @param {string} root корінь репо
|
|
2377
|
-
* @param {(msg: string) => void} fail реєстрація порушення
|
|
2378
|
-
* @returns {Promise<void>}
|
|
2379
|
-
*/
|
|
2380
|
-
async function auditKustomizationPatchesJson6902(rel, patches, kustAbs, rootNorm, root, fail) {
|
|
2381
|
-
if (!Array.isArray(patches)) {
|
|
2382
|
-
return
|
|
2383
|
-
}
|
|
2384
|
-
let patchIdx = 0
|
|
2385
|
-
for (const p of patches) {
|
|
2386
|
-
patchIdx++
|
|
2387
|
-
if (p !== null && typeof p === 'object' && !Array.isArray(p)) {
|
|
2388
|
-
const pr = /** @type {Record<string, unknown>} */ (p)
|
|
2389
|
-
await auditOneKustomizationJson6902Patch(rel, pr, patchIdx, kustAbs, rootNorm, root, fail)
|
|
2390
|
-
}
|
|
2391
|
-
}
|
|
2392
|
-
}
|
|
2130
|
+
// Plan B: вся audit-ланка JSON6902 (failIfJson6902RemoveAddConflictOnSamePath,
|
|
2131
|
+
// auditJson6902PatchExternalFile, auditOneKustomizationJson6902Patch,
|
|
2132
|
+
// auditKustomizationPatchesJson6902) видалена. Per-document inline JSON6902
|
|
2133
|
+
// remove+add conflict — у rego-пакеті `k8s.kustomization`. Зовнішні patch-файли
|
|
2134
|
+
// не охоплені rego-кроком (потребує FS-доступу) — це trade-off Plan B.
|
|
2393
2135
|
|
|
2394
2136
|
/**
|
|
2395
2137
|
* Один YAML-документ: якщо це Kustomization — перевірка **patches** на JSON6902 remove+add.
|
|
@@ -2401,140 +2143,18 @@ async function auditKustomizationPatchesJson6902(rel, patches, kustAbs, rootNorm
|
|
|
2401
2143
|
* @param {(msg: string) => void} fail реєстрація порушення
|
|
2402
2144
|
* @returns {Promise<void>}
|
|
2403
2145
|
*/
|
|
2404
|
-
async function auditJson6902ForKustomizationYamlDoc(rel, rootObj, kustAbs, rootNorm, root, fail) {
|
|
2405
|
-
const rec = /** @type {Record<string, unknown>} */ (rootObj)
|
|
2406
|
-
if (rec.kind !== 'Kustomization') {
|
|
2407
|
-
return
|
|
2408
|
-
}
|
|
2409
|
-
await auditKustomizationPatchesJson6902(rel, rec.patches, kustAbs, rootNorm, root, fail)
|
|
2410
|
-
}
|
|
2411
|
-
|
|
2412
|
-
/**
|
|
2413
|
-
* Один **`kustomization.yaml`**: JSON6902 remove+add на одному path.
|
|
2414
|
-
* @param {string} root корінь репозиторію
|
|
2415
|
-
* @param {string} rootNorm нормалізований корінь
|
|
2416
|
-
* @param {string} kustAbs абсолютний шлях до файлу
|
|
2417
|
-
* @param {(msg: string) => void} fail реєстрація порушення
|
|
2418
|
-
* @returns {Promise<void>}
|
|
2419
|
-
*/
|
|
2420
|
-
async function auditJson6902OneKustomizationYamlFile(root, rootNorm, kustAbs, fail) {
|
|
2421
|
-
const rel = (relative(root, kustAbs) || kustAbs).replaceAll('\\', '/')
|
|
2422
|
-
let raw
|
|
2423
|
-
try {
|
|
2424
|
-
raw = await readFile(kustAbs, 'utf8')
|
|
2425
|
-
} catch (error) {
|
|
2426
|
-
const msg = error instanceof Error ? error.message : String(error)
|
|
2427
|
-
fail(`${rel}: не вдалося прочитати для перевірки JSON6902 (${msg})`)
|
|
2428
|
-
return
|
|
2429
|
-
}
|
|
2430
|
-
const lines = toLines(raw)
|
|
2431
|
-
const body = lines.length > 0 && MODELINE_RE.test(lines[0]) ? yamlBodyAfterModeline(lines) : lines.join('\n')
|
|
2432
|
-
/** @type {import('yaml').Document[]} */
|
|
2433
|
-
let docs
|
|
2434
|
-
try {
|
|
2435
|
-
docs = parseAllDocuments(body)
|
|
2436
|
-
} catch {
|
|
2437
|
-
return
|
|
2438
|
-
}
|
|
2439
|
-
for (const doc of docs) {
|
|
2440
|
-
if (doc.errors.length === 0) {
|
|
2441
|
-
const rootObj = doc.toJSON()
|
|
2442
|
-
if (rootObj !== null && typeof rootObj === 'object' && !Array.isArray(rootObj)) {
|
|
2443
|
-
await auditJson6902ForKustomizationYamlDoc(rel, rootObj, kustAbs, rootNorm, root, fail)
|
|
2444
|
-
}
|
|
2445
|
-
}
|
|
2446
|
-
}
|
|
2447
|
-
}
|
|
2448
|
-
|
|
2449
|
-
/**
|
|
2450
|
-
* Перевіряє всі **`kustomization.yaml`** під **`k8s`**: у inline **`patch`** і у зовнішніх patch-файлах не має бути **remove** і **add** на той самий **path**.
|
|
2451
|
-
* @param {string} root корінь репозиторію
|
|
2452
|
-
* @param {string[]} yamlFilesAbs абсолютні шляхи до yaml під k8s
|
|
2453
|
-
* @param {(msg: string) => void} fail реєстрація порушення
|
|
2454
|
-
* @returns {Promise<void>}
|
|
2455
|
-
*/
|
|
2456
|
-
async function validateKustomizationJson6902NoRemoveAddSamePath(root, yamlFilesAbs, fail) {
|
|
2457
|
-
const rootNorm = resolve(root)
|
|
2458
|
-
for (const kustAbs of yamlFilesAbs.filter(p => basename(p).toLowerCase() === 'kustomization.yaml')) {
|
|
2459
|
-
await auditJson6902OneKustomizationYamlFile(root, rootNorm, kustAbs, fail)
|
|
2460
|
-
}
|
|
2461
|
-
}
|
|
2462
|
-
|
|
2463
|
-
/**
|
|
2464
|
-
* Заборонений **kind: Ingress** у документі.
|
|
2465
|
-
* @param {string} rel відносний шлях до файлу
|
|
2466
|
-
* @param {number} docIndex 1-based індекс документа
|
|
2467
|
-
* @param {Record<string, unknown>} rec корінь маніфесту
|
|
2468
|
-
* @param {(msg: string) => void} fail реєстрація помилки
|
|
2469
|
-
* @returns {void}
|
|
2470
|
-
*/
|
|
2471
|
-
function failIfIngressInDocument(rel, docIndex, rec, fail) {
|
|
2472
|
-
if (rec.kind !== 'Ingress') {
|
|
2473
|
-
return
|
|
2474
|
-
}
|
|
2475
|
-
fail(
|
|
2476
|
-
`${rel}: знайдено kind: Ingress (документ ${docIndex}) — заміни на Gateway API: HTTPRoute (hr.yaml), HealthCheckPolicy (hc.yaml) (див. k8s.mdc)`
|
|
2477
|
-
)
|
|
2478
|
-
}
|
|
2479
|
-
|
|
2480
|
-
/**
|
|
2481
|
-
* Чи маніфест використовує заборонений **`apiVersion: autoscaling/v1`** (HPA).
|
|
2482
|
-
* Канон — **`autoscaling/v2`** (див. k8s.mdc).
|
|
2483
|
-
* @param {unknown} manifest корінь YAML-документа
|
|
2484
|
-
* @returns {boolean} true, якщо `apiVersion === 'autoscaling/v1'`
|
|
2485
|
-
*/
|
|
2486
|
-
export function isForbiddenAutoscalingV1Manifest(manifest) {
|
|
2487
|
-
if (manifest === null || manifest === undefined || typeof manifest !== 'object' || Array.isArray(manifest))
|
|
2488
|
-
return false
|
|
2489
|
-
const rec = /** @type {Record<string, unknown>} */ (manifest)
|
|
2490
|
-
return rec.apiVersion === 'autoscaling/v1'
|
|
2491
|
-
}
|
|
2492
|
-
|
|
2493
2146
|
/**
|
|
2494
|
-
*
|
|
2495
|
-
*
|
|
2496
|
-
*
|
|
2497
|
-
*
|
|
2498
|
-
* @param {(msg: string) => void} fail реєстрація помилки
|
|
2499
|
-
* @returns {void}
|
|
2147
|
+
* Plan B: per-document JSON6902 remove+add conflict — у rego-пакеті
|
|
2148
|
+
* `k8s.kustomization`, виклик через `runAllK8sRego`. JS-функції
|
|
2149
|
+
* auditJson6902ForKustomizationYamlDoc, auditJson6902OneKustomizationYamlFile,
|
|
2150
|
+
* validateKustomizationJson6902NoRemoveAddSamePath видалено.
|
|
2500
2151
|
*/
|
|
2501
|
-
function failIfAutoscalingV1InDocument(rel, docIndex, rec, fail) {
|
|
2502
|
-
if (!isForbiddenAutoscalingV1Manifest(rec)) {
|
|
2503
|
-
return
|
|
2504
|
-
}
|
|
2505
|
-
const kind = typeof rec.kind === 'string' ? rec.kind : '(невідомо)'
|
|
2506
|
-
fail(
|
|
2507
|
-
`${rel}: знайдено apiVersion: autoscaling/v1 (документ ${docIndex}, kind: ${kind}) — мігруй на autoscaling/v2 (див. k8s.mdc)`
|
|
2508
|
-
)
|
|
2509
|
-
}
|
|
2510
2152
|
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
* @returns {void}
|
|
2517
|
-
*/
|
|
2518
|
-
function scanForbiddenManifestsInYamlDocuments(rel, body, fail) {
|
|
2519
|
-
/** @type {import('yaml').Document[]} */
|
|
2520
|
-
let docs
|
|
2521
|
-
try {
|
|
2522
|
-
docs = parseAllDocuments(body)
|
|
2523
|
-
} catch {
|
|
2524
|
-
return
|
|
2525
|
-
}
|
|
2526
|
-
|
|
2527
|
-
for (const [di, doc] of docs.entries()) {
|
|
2528
|
-
if (doc.errors.length === 0) {
|
|
2529
|
-
const obj = doc.toJSON()
|
|
2530
|
-
if (obj !== null && typeof obj === 'object' && !Array.isArray(obj)) {
|
|
2531
|
-
const rec = /** @type {Record<string, unknown>} */ (obj)
|
|
2532
|
-
failIfIngressInDocument(rel, di + 1, rec, fail)
|
|
2533
|
-
failIfAutoscalingV1InDocument(rel, di + 1, rec, fail)
|
|
2534
|
-
}
|
|
2535
|
-
}
|
|
2536
|
-
}
|
|
2537
|
-
}
|
|
2153
|
+
// Plan B: per-document `kind: Ingress` і `apiVersion: autoscaling/v1` заборонено —
|
|
2154
|
+
// у rego-пакеті `k8s.manifest`, виклик через `runAllK8sRego`. JS-функції
|
|
2155
|
+
// failIfIngressInDocument, failIfAutoscalingV1InDocument, scanForbiddenManifestsInYamlDocuments
|
|
2156
|
+
// видалено. `isForbiddenAutoscalingV1Manifest` як публічний predicate теж видалено
|
|
2157
|
+
// (rego — авторитативне джерело).
|
|
2538
2158
|
|
|
2539
2159
|
/**
|
|
2540
2160
|
* Рекомендоване **`resources.requests.cpu`** поза шарем base для підказок у повідомленнях (k8s.mdc).
|
|
@@ -3086,15 +2706,6 @@ export function serviceForbiddenGcpAnnotationsViolation(manifest) {
|
|
|
3086
2706
|
/** Суфікс **`metadata.name`** headless-сервісу поруч із **`svc.yaml`** (див. k8s.mdc). */
|
|
3087
2707
|
const SVC_HL_NAME_SUFFIX = '-hl'
|
|
3088
2708
|
|
|
3089
|
-
/**
|
|
3090
|
-
* Kind маршрутів Gateway API, у **`spec`** яких шукаємо **`backendRefs`** / **`backendRef`** до **Service**.
|
|
3091
|
-
* @type {Set<string>}
|
|
3092
|
-
*/
|
|
3093
|
-
const GATEWAY_API_ROUTE_KINDS = new Set(['HTTPRoute', 'GRPCRoute', 'TCPRoute', 'TLSRoute', 'UDPRoute'])
|
|
3094
|
-
|
|
3095
|
-
/** Префікс **`apiVersion`** стандартних ресурсів Gateway API. */
|
|
3096
|
-
const GATEWAY_API_GROUP_PREFIX = 'gateway.networking.k8s.io/'
|
|
3097
|
-
|
|
3098
2709
|
/**
|
|
3099
2710
|
* Чи **Service** у **`svc.yaml`** має **`spec.type: ClusterIP`** (k8s.mdc).
|
|
3100
2711
|
* @param {unknown} manifest корінь YAML-документа
|
|
@@ -3281,64 +2892,9 @@ export function collectGatewayApiRouteBackendRefsWithRedundantNamespace(spec, ro
|
|
|
3281
2892
|
* @param {(msg: string) => void} fail callback помилки
|
|
3282
2893
|
* @returns {void}
|
|
3283
2894
|
*/
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
if (
|
|
3288
|
-
typeof av !== 'string' ||
|
|
3289
|
-
!av.startsWith(GATEWAY_API_GROUP_PREFIX) ||
|
|
3290
|
-
typeof kind !== 'string' ||
|
|
3291
|
-
!GATEWAY_API_ROUTE_KINDS.has(kind)
|
|
3292
|
-
) {
|
|
3293
|
-
return
|
|
3294
|
-
}
|
|
3295
|
-
const names = collectGatewayApiRouteBackendServiceNames(rec.spec)
|
|
3296
|
-
for (const svcName of names) {
|
|
3297
|
-
if (!svcName.endsWith(SVC_HL_NAME_SUFFIX)) {
|
|
3298
|
-
fail(
|
|
3299
|
-
`${rel}: Gateway API ${kind} (документ ${docIndex}): backendRef до Service має вказувати headless-сервіс з суфіксом «${SVC_HL_NAME_SUFFIX}» у name (зараз: «${svcName}»; див. k8s.mdc)`
|
|
3300
|
-
)
|
|
3301
|
-
}
|
|
3302
|
-
}
|
|
3303
|
-
const meta = rec.metadata
|
|
3304
|
-
if (meta !== null && typeof meta === 'object' && !Array.isArray(meta)) {
|
|
3305
|
-
const routeNs = /** @type {Record<string, unknown>} */ (meta).namespace
|
|
3306
|
-
if (typeof routeNs === 'string' && routeNs !== '') {
|
|
3307
|
-
const redundant = collectGatewayApiRouteBackendRefsWithRedundantNamespace(rec.spec, routeNs)
|
|
3308
|
-
for (const svcName of redundant) {
|
|
3309
|
-
fail(
|
|
3310
|
-
`${rel}: Gateway API ${kind} (документ ${docIndex}): backendRef «${svcName}» має namespace «${routeNs}», що збігається з metadata.namespace маршруту — прибери поле namespace з backendRef (див. k8s.mdc)`
|
|
3311
|
-
)
|
|
3312
|
-
}
|
|
3313
|
-
}
|
|
3314
|
-
}
|
|
3315
|
-
}
|
|
3316
|
-
|
|
3317
|
-
/**
|
|
3318
|
-
* Реєструє порушення: маршрути Gateway API мають посилатися на **Service** з суфіксом **`-hl`**.
|
|
3319
|
-
* @param {string} rel відносний шлях до файлу
|
|
3320
|
-
* @param {string} body YAML після modeline
|
|
3321
|
-
* @param {(msg: string) => void} fail callback помилки
|
|
3322
|
-
* @returns {void}
|
|
3323
|
-
*/
|
|
3324
|
-
function scanGatewayApiRouteBackendRefsInYamlBody(rel, body, fail) {
|
|
3325
|
-
/** @type {import('yaml').Document[]} */
|
|
3326
|
-
let docs
|
|
3327
|
-
try {
|
|
3328
|
-
docs = parseAllDocuments(body)
|
|
3329
|
-
} catch {
|
|
3330
|
-
return
|
|
3331
|
-
}
|
|
3332
|
-
|
|
3333
|
-
for (const [di, doc] of docs.entries()) {
|
|
3334
|
-
if (doc.errors.length === 0) {
|
|
3335
|
-
const obj = doc.toJSON()
|
|
3336
|
-
if (obj !== null && typeof obj === 'object' && !Array.isArray(obj)) {
|
|
3337
|
-
failIfGatewayRouteUsesNonHeadlessService(rel, di + 1, /** @type {Record<string, unknown>} */ (obj), fail)
|
|
3338
|
-
}
|
|
3339
|
-
}
|
|
3340
|
-
}
|
|
3341
|
-
}
|
|
2895
|
+
// Plan B: Gateway API маршрут backendRef з суфіксом `-hl` і redundant namespace —
|
|
2896
|
+
// у rego-пакеті `k8s.gateway`, виклик через `runAllK8sRego`. JS-функції
|
|
2897
|
+
// failIfGatewayRouteUsesNonHeadlessService, scanGatewayApiRouteBackendRefsInYamlBody видалено.
|
|
3342
2898
|
|
|
3343
2899
|
/**
|
|
3344
2900
|
* Звузити `unknown` до `Record<string, unknown>` (`null`, масиви, примітиви → null).
|
|
@@ -3815,36 +3371,28 @@ async function validateHasuraHttpRouteCanon(root, yamlFiles, fail) {
|
|
|
3815
3371
|
const { hasuraByDir, httpRoutes } = await collectHasuraDeploymentsAndHttpRoutes(yamlFiles)
|
|
3816
3372
|
if (hasuraByDir.size === 0 || httpRoutes.length === 0) return
|
|
3817
3373
|
|
|
3374
|
+
// JS gating: відберемо файли HTTPRoute, що paired з Hasura-Deployment у тому ж каталозі
|
|
3375
|
+
// (за `metadata.name` HTTPRoute === metadata.name Hasura-Deployment). Per-document валідація
|
|
3376
|
+
// канону 4 правил Hasura — у rego-пакеті `k8s.hasura_httproute`.
|
|
3377
|
+
const pairedFiles = new Set()
|
|
3818
3378
|
for (const hr of httpRoutes) {
|
|
3819
3379
|
const meta = asPlainRecord(hr.obj.metadata)
|
|
3820
3380
|
const name = meta === null ? undefined : meta.name
|
|
3821
3381
|
const set = typeof name === 'string' && name !== '' ? hasuraByDir.get(hr.dir) : undefined
|
|
3822
3382
|
if (set !== undefined && typeof name === 'string' && set.has(name)) {
|
|
3823
|
-
|
|
3824
|
-
if (v !== null) {
|
|
3825
|
-
const rel = (relative(root, hr.abs) || hr.abs).replaceAll('\\', '/')
|
|
3826
|
-
fail(
|
|
3827
|
-
`${rel}: HTTPRoute «${name}» (документ ${hr.docIndex}; прив'язано до Hasura-Deployment у тому ж каталозі): ${v}`
|
|
3828
|
-
)
|
|
3829
|
-
}
|
|
3383
|
+
pairedFiles.add(hr.abs)
|
|
3830
3384
|
}
|
|
3831
3385
|
}
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
return null
|
|
3842
|
-
const rec = /** @type {Record<string, unknown>} */ (manifest)
|
|
3843
|
-
const meta = rec.metadata
|
|
3844
|
-
if (meta !== null && typeof meta === 'object' && !Array.isArray(meta) && 'namespace' in meta) {
|
|
3845
|
-
return 'metadata.namespace заборонено — namespace задає kustomization.yaml (поле namespace); файл підключено через resources / patches / … (див. k8s.mdc)'
|
|
3386
|
+
if (pairedFiles.size === 0) return
|
|
3387
|
+
const violations = runConftestBatch({
|
|
3388
|
+
policyDirRel: 'k8s/hasura_httproute',
|
|
3389
|
+
namespace: 'k8s.hasura_httproute',
|
|
3390
|
+
files: [...pairedFiles]
|
|
3391
|
+
})
|
|
3392
|
+
for (const v of violations) {
|
|
3393
|
+
const rel = (relative(root, v.filename) || v.filename).replaceAll('\\', '/')
|
|
3394
|
+
fail(`${rel}: ${v.message}`)
|
|
3846
3395
|
}
|
|
3847
|
-
return null
|
|
3848
3396
|
}
|
|
3849
3397
|
|
|
3850
3398
|
/**
|
|
@@ -3898,117 +3446,11 @@ export function isK8sBaseManifestYamlPath(rel, baseLower) {
|
|
|
3898
3446
|
return K8S_BASE_SEGMENT_RE.test(n)
|
|
3899
3447
|
}
|
|
3900
3448
|
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
* @param {boolean} skipMetaNs пропуск для **kustomization.yaml**
|
|
3907
|
-
* @param {boolean} inBaseManifest файл у **k8s/base/**
|
|
3908
|
-
* @param {boolean} kustomizeManaged файл у графі kustomization
|
|
3909
|
-
* @param {(msg: string) => void} fail реєстрація помилки
|
|
3910
|
-
* @returns {void}
|
|
3911
|
-
*/
|
|
3912
|
-
function failIfK8sPolicyNamespaceRulesViolated(rel, docIndex, obj, skipMetaNs, inBaseManifest, kustomizeManaged, fail) {
|
|
3913
|
-
if (skipMetaNs) {
|
|
3914
|
-
return
|
|
3915
|
-
}
|
|
3916
|
-
if (inBaseManifest) {
|
|
3917
|
-
const req = metadataNamespaceRequiredViolation(obj, true)
|
|
3918
|
-
if (req !== null) {
|
|
3919
|
-
fail(`${rel}: документ ${docIndex}: ${req}`)
|
|
3920
|
-
}
|
|
3921
|
-
return
|
|
3922
|
-
}
|
|
3923
|
-
if (kustomizeManaged) {
|
|
3924
|
-
const ns = metadataNamespaceForbiddenViolation(obj)
|
|
3925
|
-
if (ns !== null) {
|
|
3926
|
-
fail(`${rel}: документ ${docIndex}: ${ns}`)
|
|
3927
|
-
}
|
|
3928
|
-
return
|
|
3929
|
-
}
|
|
3930
|
-
const req = metadataNamespaceRequiredViolation(obj, false)
|
|
3931
|
-
if (req !== null) {
|
|
3932
|
-
fail(`${rel}: документ ${docIndex}: ${req}`)
|
|
3933
|
-
}
|
|
3934
|
-
}
|
|
3935
|
-
|
|
3936
|
-
/**
|
|
3937
|
-
* Deployment / Service / HealthCheckPolicy — політики для одного документа.
|
|
3938
|
-
* @param {string} rel відносний шлях
|
|
3939
|
-
* @param {string} baseLower basename (нижній регістр)
|
|
3940
|
-
* @param {number} docIndex 1-based
|
|
3941
|
-
* @param {unknown} obj корінь документа
|
|
3942
|
-
* @param {(msg: string) => void} fail реєстрація помилки
|
|
3943
|
-
* @returns {void}
|
|
3944
|
-
*/
|
|
3945
|
-
function failIfK8sPolicyResourceRulesViolated(rel, baseLower, docIndex, obj, fail) {
|
|
3946
|
-
const relPosix = rel.replaceAll('\\', '/')
|
|
3947
|
-
const inK8sBaseLayer = isK8sYamlUnderBaseDirectory(relPosix)
|
|
3948
|
-
const resV = deploymentResourcesViolation(obj, inK8sBaseLayer)
|
|
3949
|
-
if (resV !== null) {
|
|
3950
|
-
fail(`${rel}: Deployment (документ ${docIndex}): ${resV}`)
|
|
3951
|
-
}
|
|
3952
|
-
const hasuraV = deploymentHasuraGraphqlEngineImageViolation(obj)
|
|
3953
|
-
if (hasuraV !== null) {
|
|
3954
|
-
fail(`${rel}: Deployment (документ ${docIndex}): ${hasuraV}`)
|
|
3955
|
-
}
|
|
3956
|
-
const svcGcpV = serviceForbiddenGcpAnnotationsViolation(obj)
|
|
3957
|
-
if (svcGcpV !== null) {
|
|
3958
|
-
fail(`${rel}: Service (документ ${docIndex}): ${svcGcpV}`)
|
|
3959
|
-
}
|
|
3960
|
-
if (baseLower === 'svc.yaml') {
|
|
3961
|
-
const svcT = serviceSvcYamlClusterIpTypeViolation(obj)
|
|
3962
|
-
if (svcT !== null) {
|
|
3963
|
-
fail(`${rel}: Service (документ ${docIndex}): ${svcT}`)
|
|
3964
|
-
}
|
|
3965
|
-
}
|
|
3966
|
-
if (baseLower === 'svc-hl.yaml') {
|
|
3967
|
-
const svcH = serviceSvcHlYamlHeadlessViolation(obj)
|
|
3968
|
-
if (svcH !== null) {
|
|
3969
|
-
fail(`${rel}: Service (документ ${docIndex}): ${svcH}`)
|
|
3970
|
-
}
|
|
3971
|
-
}
|
|
3972
|
-
const hcpHl = healthCheckPolicyTargetRefHeadlessServiceViolation(obj)
|
|
3973
|
-
if (hcpHl !== null) {
|
|
3974
|
-
fail(`${rel}: документ ${docIndex}: ${hcpHl}`)
|
|
3975
|
-
}
|
|
3976
|
-
}
|
|
3977
|
-
|
|
3978
|
-
/**
|
|
3979
|
-
* Парсить усі YAML-документи: **metadata.namespace**, **Deployment.resources**, **Hasura image pin**,
|
|
3980
|
-
* **Service — заборонені GKE-анотації**, **`svc.yaml`** (**`spec.type: ClusterIP`**), **`svc-hl.yaml`**
|
|
3981
|
-
* (**headless**, суфікс **`-hl`** у **`metadata.name`**), **HealthCheckPolicy** (**`targetRef.name`** з **`-hl`**).
|
|
3982
|
-
* @param {string} rel відносний шлях
|
|
3983
|
-
* @param {string} baseLower basename файлу (нижній регістр)
|
|
3984
|
-
* @param {string} body вміст після modeline
|
|
3985
|
-
* @param {(msg: string) => void} fail реєстрація помилки
|
|
3986
|
-
* @param {boolean} kustomizeManaged чи файл досяжний з kustomization.yaml (resources / patches / …)
|
|
3987
|
-
*/
|
|
3988
|
-
function validateK8sYamlPolicyDocuments(rel, baseLower, body, fail, kustomizeManaged) {
|
|
3989
|
-
/** @type {import('yaml').Document[]} */
|
|
3990
|
-
let docs
|
|
3991
|
-
try {
|
|
3992
|
-
docs = parseAllDocuments(body)
|
|
3993
|
-
} catch (error) {
|
|
3994
|
-
const msg = error instanceof Error ? error.message : String(error)
|
|
3995
|
-
fail(`${rel}: не вдалося розібрати YAML для перевірок маніфестів (${msg})`)
|
|
3996
|
-
return
|
|
3997
|
-
}
|
|
3998
|
-
|
|
3999
|
-
const skipMetaNs = isKustomizationFileName(baseLower)
|
|
4000
|
-
const inBaseManifest = isK8sBaseManifestYamlPath(rel, baseLower)
|
|
4001
|
-
|
|
4002
|
-
for (const [di, doc] of docs.entries()) {
|
|
4003
|
-
if (doc.errors.length > 0) {
|
|
4004
|
-
fail(`${rel}: YAML (документ ${di + 1}): ${doc.errors.map(e => e.message).join('; ')}`)
|
|
4005
|
-
} else {
|
|
4006
|
-
const obj = doc.toJSON()
|
|
4007
|
-
failIfK8sPolicyNamespaceRulesViolated(rel, di + 1, obj, skipMetaNs, inBaseManifest, kustomizeManaged, fail)
|
|
4008
|
-
failIfK8sPolicyResourceRulesViolated(rel, baseLower, di + 1, obj, fail)
|
|
4009
|
-
}
|
|
4010
|
-
}
|
|
4011
|
-
}
|
|
3449
|
+
// Plan B: per-document валідаційне ядро для k8s YAML повністю в rego —
|
|
3450
|
+
// `k8s.manifest`, `k8s.gateway`, `k8s.svc_yaml`, `k8s.svc_hl_yaml`,
|
|
3451
|
+
// `k8s.base_manifest`. Виклик через `runAllK8sRego`.
|
|
3452
|
+
// JS-функції failIfK8sPolicyNamespaceRulesViolated, failIfK8sPolicyResourceRulesViolated,
|
|
3453
|
+
// validateK8sYamlPolicyDocuments видалено.
|
|
4012
3454
|
|
|
4013
3455
|
/**
|
|
4014
3456
|
* Kind для імен файлів yannh/datree: лише літери та цифри, нижній регістр (Service → service, HTTPRoute → httproute).
|
|
@@ -4103,20 +3545,6 @@ function countSchemaModelines(lines) {
|
|
|
4103
3545
|
return lines.filter(l => OXLINT_SCHEMA_MODELINE_RE.test(l.trim())).length
|
|
4104
3546
|
}
|
|
4105
3547
|
|
|
4106
|
-
/**
|
|
4107
|
-
* Політики маніфестів і Gateway backendRefs після розбору тіла.
|
|
4108
|
-
* @param {string} rel відносний шлях
|
|
4109
|
-
* @param {string} baseLower basename (нижній регістр)
|
|
4110
|
-
* @param {string} body YAML після modeline
|
|
4111
|
-
* @param {(msg: string) => void} fail реєстрація помилки
|
|
4112
|
-
* @param {Set<string>} kustomizeManagedRel kustomize-managed шляхи
|
|
4113
|
-
* @returns {void}
|
|
4114
|
-
*/
|
|
4115
|
-
function runK8sYamlPolicyAndGatewayScans(rel, baseLower, body, fail, kustomizeManagedRel) {
|
|
4116
|
-
const kustomizeManaged = kustomizeManagedRel.has(rel)
|
|
4117
|
-
validateK8sYamlPolicyDocuments(rel, baseLower, body, fail, kustomizeManaged)
|
|
4118
|
-
scanGatewayApiRouteBackendRefsInYamlBody(rel, body, fail)
|
|
4119
|
-
}
|
|
4120
3548
|
|
|
4121
3549
|
/**
|
|
4122
3550
|
* Файл з першим документом **HttpBackendGroup** (ALB Yandex): без modeline **$schema**.
|
|
@@ -4125,14 +3553,13 @@ function runK8sYamlPolicyAndGatewayScans(rel, baseLower, body, fail, kustomizeMa
|
|
|
4125
3553
|
* @param {string[]} lines рядки файлу
|
|
4126
3554
|
* @param {(msg: string) => void} fail реєстрація помилки
|
|
4127
3555
|
* @param {(msg: string) => void} pass реєстрація успіху
|
|
4128
|
-
* @param {Set<string>} kustomizeManagedRel kustomize-managed шляхи
|
|
4129
3556
|
* @returns {void}
|
|
4130
3557
|
*/
|
|
4131
|
-
function checkK8sYamlHttpBackendGroupFile(rel,
|
|
4132
|
-
|
|
4133
|
-
|
|
3558
|
+
function checkK8sYamlHttpBackendGroupFile(rel, _baseLower, _lines, _fail, pass) {
|
|
3559
|
+
// Per-document валідація (Ingress/autoscaling/v1 заборонено, Gateway API backendRef,
|
|
3560
|
+
// metadata.namespace правила) — у rego (`k8s.manifest`, `k8s.gateway`, `k8s.base_manifest`),
|
|
3561
|
+
// батч-виклик з `runAllK8sRego` на початку `check()`.
|
|
4134
3562
|
pass(`${rel}: HttpBackendGroup (alb.yc.io/v1alpha1) — modeline $schema не застосовується (k8s.mdc)`)
|
|
4135
|
-
runK8sYamlPolicyAndGatewayScans(rel, baseLower, body, fail, kustomizeManagedRel)
|
|
4136
3563
|
}
|
|
4137
3564
|
|
|
4138
3565
|
/**
|
|
@@ -4143,10 +3570,9 @@ function checkK8sYamlHttpBackendGroupFile(rel, baseLower, lines, fail, pass, kus
|
|
|
4143
3570
|
* @param {string[]} lines рядки файлу
|
|
4144
3571
|
* @param {(msg: string) => void} fail реєстрація помилки
|
|
4145
3572
|
* @param {(msg: string) => void} pass реєстрація успіху
|
|
4146
|
-
* @param {Set<string>} kustomizeManagedRel kustomize-managed шляхи
|
|
4147
3573
|
* @returns {void}
|
|
4148
3574
|
*/
|
|
4149
|
-
function checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pass
|
|
3575
|
+
function checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pass) {
|
|
4150
3576
|
const match = lines[0].match(MODELINE_RE)
|
|
4151
3577
|
if (!match) {
|
|
4152
3578
|
fail(`${rel}: некоректний modeline $schema у першому рядку`)
|
|
@@ -4160,7 +3586,9 @@ function checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pa
|
|
|
4160
3586
|
|
|
4161
3587
|
const body = yamlBodyAfterModeline(lines)
|
|
4162
3588
|
|
|
4163
|
-
|
|
3589
|
+
// Per-document валідація (Ingress/autoscaling/v1 заборонено, Gateway API backendRef,
|
|
3590
|
+
// metadata.namespace правила, Service GCP-анотації, Deployment resources/Hasura image,
|
|
3591
|
+
// topologySpread, HCP, svc/svc-hl) — делегована rego, виконано у `runAllK8sRego` вище.
|
|
4164
3592
|
|
|
4165
3593
|
if (schemaUrl.startsWith('file:')) {
|
|
4166
3594
|
pass(`${rel}: локальна схема (file:) — перевірка URL за apiVersion/kind пропущена`)
|
|
@@ -4181,10 +3609,7 @@ function checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pa
|
|
|
4181
3609
|
pass(`${rel}: $schema узгоджено (${reason})`)
|
|
4182
3610
|
} else {
|
|
4183
3611
|
fail(`${rel}: $schema має бути https URL або file: (див. k8s.mdc)`)
|
|
4184
|
-
return
|
|
4185
3612
|
}
|
|
4186
|
-
|
|
4187
|
-
runK8sYamlPolicyAndGatewayScans(rel, baseLower, body, fail, kustomizeManagedRel)
|
|
4188
3613
|
}
|
|
4189
3614
|
|
|
4190
3615
|
/**
|
|
@@ -4193,10 +3618,9 @@ function checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pa
|
|
|
4193
3618
|
* @param {string} root корінь репозиторію
|
|
4194
3619
|
* @param {(msg: string) => void} fail реєстрація помилки
|
|
4195
3620
|
* @param {(msg: string) => void} pass реєстрація успіху
|
|
4196
|
-
* @param {Set<string>} kustomizeManagedRel відносні posix-шляхи з collectKustomizeManagedRelPaths
|
|
4197
3621
|
* @returns {Promise<void>}
|
|
4198
3622
|
*/
|
|
4199
|
-
async function checkK8sYamlFile(abs, root, fail, pass
|
|
3623
|
+
async function checkK8sYamlFile(abs, root, fail, pass) {
|
|
4200
3624
|
const rel = (relative(root, abs) || abs).replaceAll('\\', '/')
|
|
4201
3625
|
const base = basename(abs)
|
|
4202
3626
|
const baseLower = base.toLowerCase()
|
|
@@ -4237,7 +3661,7 @@ async function checkK8sYamlFile(abs, root, fail, pass, kustomizeManagedRel) {
|
|
|
4237
3661
|
)
|
|
4238
3662
|
return
|
|
4239
3663
|
}
|
|
4240
|
-
checkK8sYamlHttpBackendGroupFile(rel, baseLower, lines, fail, pass
|
|
3664
|
+
checkK8sYamlHttpBackendGroupFile(rel, baseLower, lines, fail, pass)
|
|
4241
3665
|
return
|
|
4242
3666
|
}
|
|
4243
3667
|
|
|
@@ -4246,7 +3670,7 @@ async function checkK8sYamlFile(abs, root, fail, pass, kustomizeManagedRel) {
|
|
|
4246
3670
|
return
|
|
4247
3671
|
}
|
|
4248
3672
|
|
|
4249
|
-
checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pass
|
|
3673
|
+
checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pass)
|
|
4250
3674
|
}
|
|
4251
3675
|
|
|
4252
3676
|
/**
|
|
@@ -4272,46 +3696,9 @@ function assertNoForbiddenK8sDevPaths(yamlFiles, root, fail) {
|
|
|
4272
3696
|
* @param {(msg: string) => void} fail реєстрація порушення
|
|
4273
3697
|
* @returns {Promise<void>}
|
|
4274
3698
|
*/
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
const raw = await readFile(abs, 'utf8')
|
|
4279
|
-
const lines = toLines(raw)
|
|
4280
|
-
const body = yamlBodyAfterModeline(lines)
|
|
4281
|
-
/** @type {import('yaml').Document[] | undefined} */
|
|
4282
|
-
let docs
|
|
4283
|
-
try {
|
|
4284
|
-
docs = parseAllDocuments(body)
|
|
4285
|
-
} catch {
|
|
4286
|
-
fail(`${rel}: не вдалося розпарсити YAML для перевірки namespace у base (див. k8s.mdc)`)
|
|
4287
|
-
return
|
|
4288
|
-
}
|
|
4289
|
-
const first = docs[0]?.toJSON()
|
|
4290
|
-
const v = baseKustomizationNamespaceViolation(first)
|
|
4291
|
-
if (v) {
|
|
4292
|
-
fail(`${rel}: ${v}`)
|
|
4293
|
-
}
|
|
4294
|
-
} catch (error) {
|
|
4295
|
-
const msg = error instanceof Error ? error.message : String(error)
|
|
4296
|
-
fail(`${rel}: не вдалося прочитати (${msg})`)
|
|
4297
|
-
}
|
|
4298
|
-
}
|
|
4299
|
-
|
|
4300
|
-
/**
|
|
4301
|
-
* Якщо є **`k8s/base/kustomization.yaml`**, у ньому **завжди** має бути непорожній **`namespace:`**.
|
|
4302
|
-
* @param {string} root корінь репозиторію
|
|
4303
|
-
* @param {string[]} yamlFiles абсолютні шляхи
|
|
4304
|
-
* @param {(msg: string) => void} fail callback для реєстрації порушення
|
|
4305
|
-
* @returns {Promise<void>}
|
|
4306
|
-
*/
|
|
4307
|
-
async function ensureBaseKustomizationHasNamespace(root, yamlFiles, fail) {
|
|
4308
|
-
for (const abs of yamlFiles) {
|
|
4309
|
-
const rel = relative(root, abs).replaceAll('\\', '/')
|
|
4310
|
-
if (isBaseKustomizationPath(rel)) {
|
|
4311
|
-
await verifyBaseKustomizationNamespaceOnFile(root, abs, fail)
|
|
4312
|
-
}
|
|
4313
|
-
}
|
|
4314
|
-
}
|
|
3699
|
+
// Plan B: per-document `k8s/base/kustomization.yaml` має непорожнє поле `namespace:` —
|
|
3700
|
+
// у rego-пакеті `k8s.base_kustomization`, виклик через `runAllK8sRego`.
|
|
3701
|
+
// JS-функції verifyBaseKustomizationNamespaceOnFile, ensureBaseKustomizationHasNamespace видалено.
|
|
4315
3702
|
|
|
4316
3703
|
const CONFIGMAP_BASE_PATH_RE = /\/k8s\/base\/configmap\.yaml$/u
|
|
4317
3704
|
|
|
@@ -4374,19 +3761,6 @@ async function validateConfigMapNameMatchesDeployment(root, yamlFilesAbs, fail,
|
|
|
4374
3761
|
}
|
|
4375
3762
|
}
|
|
4376
3763
|
|
|
4377
|
-
/**
|
|
4378
|
-
* Знаходить перший документ **ConfigMap** у файлі (з `metadata.name`).
|
|
4379
|
-
* @param {string} absPath абсолютний шлях до YAML-файлу
|
|
4380
|
-
* @returns {Promise<Record<string, unknown> | null>} об'єкт ConfigMap або null
|
|
4381
|
-
*/
|
|
4382
|
-
async function readFirstConfigMapDoc(absPath) {
|
|
4383
|
-
const raw = await tryReadFileUtf8(absPath)
|
|
4384
|
-
if (raw === undefined) return null
|
|
4385
|
-
const docs = tryParseAllYamlDocs(raw)
|
|
4386
|
-
if (docs === undefined) return null
|
|
4387
|
-
return findFirstDocByKind(docs, 'ConfigMap')
|
|
4388
|
-
}
|
|
4389
|
-
|
|
4390
3764
|
/**
|
|
4391
3765
|
* Для кожного `k8s/base/configmap.yaml`, у каталозі якого поруч є Hasura-Deployment,
|
|
4392
3766
|
* вимагає у `data` ключ **`HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS`** зі значенням **`"true"`** (k8s.mdc).
|
|
@@ -4400,21 +3774,29 @@ async function validateHasuraConfigMapRemoteSchemaPermissions(root, yamlFilesAbs
|
|
|
4400
3774
|
const rel = relative(root, abs).replaceAll('\\', '/')
|
|
4401
3775
|
return CONFIGMAP_BASE_PATH_RE.test(`/${rel}`) || rel === 'k8s/base/configmap.yaml'
|
|
4402
3776
|
})
|
|
3777
|
+
// JS gating: відберемо ConfigMap-файли, у каталозі яких поруч є Hasura-Deployment.
|
|
3778
|
+
// Per-document валідація `data.HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS == "true"`
|
|
3779
|
+
// — у rego-пакеті `k8s.hasura_configmap`.
|
|
3780
|
+
const paired = []
|
|
4403
3781
|
for (const cmAbs of cmFiles) {
|
|
4404
|
-
const rel = relative(root, cmAbs).replaceAll('\\', '/') || cmAbs
|
|
4405
3782
|
const deployment = await findDeploymentDocInDir(dirname(cmAbs))
|
|
4406
3783
|
if (deployment !== null && isHasuraDeploymentManifest(deployment)) {
|
|
4407
|
-
|
|
4408
|
-
if (cm !== null) {
|
|
4409
|
-
const violation = hasuraConfigMapRemoteSchemaPermissionsViolation(cm)
|
|
4410
|
-
if (violation === null) {
|
|
4411
|
-
passFn(`${rel}: ${HASURA_REMOTE_SCHEMA_PERMISSIONS_KEY}="true" для Hasura-Deployment (k8s.mdc)`)
|
|
4412
|
-
} else {
|
|
4413
|
-
fail(`${rel}: ${violation}`)
|
|
4414
|
-
}
|
|
4415
|
-
}
|
|
3784
|
+
paired.push(cmAbs)
|
|
4416
3785
|
}
|
|
4417
3786
|
}
|
|
3787
|
+
if (paired.length === 0) return
|
|
3788
|
+
const violations = runConftestBatch({
|
|
3789
|
+
policyDirRel: 'k8s/hasura_configmap',
|
|
3790
|
+
namespace: 'k8s.hasura_configmap',
|
|
3791
|
+
files: paired
|
|
3792
|
+
})
|
|
3793
|
+
for (const v of violations) {
|
|
3794
|
+
const rel = (relative(root, v.filename) || v.filename).replaceAll('\\', '/')
|
|
3795
|
+
fail(`${rel}: ${v.message}`)
|
|
3796
|
+
}
|
|
3797
|
+
if (violations.length === 0) {
|
|
3798
|
+
passFn(`Hasura-ConfigMap (${paired.length}) відповідає ${HASURA_REMOTE_SCHEMA_PERMISSIONS_KEY}="true" (rego)`)
|
|
3799
|
+
}
|
|
4418
3800
|
}
|
|
4419
3801
|
|
|
4420
3802
|
/**
|
|
@@ -6518,6 +5900,53 @@ async function runKustomizationImagesCleanup(kustAbs, rel, fail, pass) {
|
|
|
6518
5900
|
* Перевіряє відповідність проєкту правилам k8s.mdc.
|
|
6519
5901
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
6520
5902
|
*/
|
|
5903
|
+
/**
|
|
5904
|
+
* Plan B (rego-authoritative): на початку `check()` батч-викликаємо path-фільтровані
|
|
5905
|
+
* rego-пакети з `npm/policy/k8s/` через `runConftestBatch`. Пакети hasura_configmap і
|
|
5906
|
+
* hasura_httproute мають cross-file gating (паруються з Hasura-Deployment) — вони запускаються
|
|
5907
|
+
* з відповідних orchestrator-функцій (`validateHasuraConfigMapRemoteSchemaPermissions`,
|
|
5908
|
+
* `validateHasuraHttpRouteCanon`). Структурна частина HPA/PDB (`k8s.hpa_pdb`) тут на всіх yaml,
|
|
5909
|
+
* env-залежні межі min/maxReplicas і expected-name — JS-cross-file у `validateDeploymentHpaPdbAndTopology`.
|
|
5910
|
+
* @param {string} root корінь репозиторію (cwd)
|
|
5911
|
+
* @param {string[]} yamlFiles абсолютні шляхи знайдених *.yaml під `…/k8s/`
|
|
5912
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
5913
|
+
* @returns {void}
|
|
5914
|
+
*/
|
|
5915
|
+
function runAllK8sRego(root, yamlFiles, fail) {
|
|
5916
|
+
const relOf = abs => relative(root, abs).replaceAll('\\', '/') || abs
|
|
5917
|
+
|
|
5918
|
+
const allYaml = yamlFiles
|
|
5919
|
+
const kustYaml = yamlFiles.filter(p => basename(p).toLowerCase() === 'kustomization.yaml')
|
|
5920
|
+
const svcYaml = yamlFiles.filter(p => basename(p) === 'svc.yaml')
|
|
5921
|
+
const svcHlYaml = yamlFiles.filter(p => basename(p) === 'svc-hl.yaml')
|
|
5922
|
+
const baseKustYaml = yamlFiles.filter(p => isBaseKustomizationPath(relOf(p)))
|
|
5923
|
+
const baseResourceYaml = yamlFiles.filter(p => {
|
|
5924
|
+
const r = relOf(p)
|
|
5925
|
+
if (!K8S_BASE_SEGMENT_RE.test(r)) return false
|
|
5926
|
+
return basename(p).toLowerCase() !== 'kustomization.yaml'
|
|
5927
|
+
})
|
|
5928
|
+
|
|
5929
|
+
/** @type {Array<{ ns: string, dir: string, files: string[] }>} */
|
|
5930
|
+
const targets = [
|
|
5931
|
+
{ ns: 'k8s.manifest', dir: 'k8s/manifest', files: allYaml },
|
|
5932
|
+
{ ns: 'k8s.gateway', dir: 'k8s/gateway', files: allYaml },
|
|
5933
|
+
{ ns: 'k8s.hpa_pdb', dir: 'k8s/hpa_pdb', files: allYaml },
|
|
5934
|
+
{ ns: 'k8s.kustomization', dir: 'k8s/kustomization', files: kustYaml },
|
|
5935
|
+
{ ns: 'k8s.svc_yaml', dir: 'k8s/svc_yaml', files: svcYaml },
|
|
5936
|
+
{ ns: 'k8s.svc_hl_yaml', dir: 'k8s/svc_hl_yaml', files: svcHlYaml },
|
|
5937
|
+
{ ns: 'k8s.base_kustomization', dir: 'k8s/base_kustomization', files: baseKustYaml },
|
|
5938
|
+
{ ns: 'k8s.base_manifest', dir: 'k8s/base_manifest', files: baseResourceYaml }
|
|
5939
|
+
]
|
|
5940
|
+
|
|
5941
|
+
for (const t of targets) {
|
|
5942
|
+
if (t.files.length === 0) continue
|
|
5943
|
+
const violations = runConftestBatch({ policyDirRel: t.dir, namespace: t.ns, files: t.files })
|
|
5944
|
+
for (const v of violations) {
|
|
5945
|
+
fail(`${relOf(v.filename)}: ${v.message}`)
|
|
5946
|
+
}
|
|
5947
|
+
}
|
|
5948
|
+
}
|
|
5949
|
+
|
|
6521
5950
|
export async function check() {
|
|
6522
5951
|
const reporter = createCheckReporter()
|
|
6523
5952
|
const { pass, fail } = reporter
|
|
@@ -6542,10 +5971,14 @@ export async function check() {
|
|
|
6542
5971
|
|
|
6543
5972
|
assertNoForbiddenK8sDevPaths(yamlFiles, root, fail)
|
|
6544
5973
|
|
|
6545
|
-
|
|
5974
|
+
// Plan B: пер-документні структурні правила — у rego-полісі `npm/policy/k8s/*`,
|
|
5975
|
+
// викликаємо одним батчем на namespace через runConftestBatch. JS нижче робить
|
|
5976
|
+
// лише cross-file orchestration, modeline та FS-existence перевірки.
|
|
5977
|
+
runAllK8sRego(root, yamlFiles, fail)
|
|
5978
|
+
pass(`Rego-полісі (npm/policy/k8s/*) виконано на ${yamlFiles.length} файл(ах)`)
|
|
6546
5979
|
|
|
6547
5980
|
for (const abs of yamlFiles) {
|
|
6548
|
-
await checkK8sYamlFile(abs, root, fail, pass
|
|
5981
|
+
await checkK8sYamlFile(abs, root, fail, pass)
|
|
6549
5982
|
}
|
|
6550
5983
|
|
|
6551
5984
|
await validateSvcYamlAndSvcHlPairs(root, yamlFiles, fail)
|
|
@@ -6554,20 +5987,12 @@ export async function check() {
|
|
|
6554
5987
|
|
|
6555
5988
|
await validateKustomizationIncludesSvcHlWithSvc(root, yamlFiles, fail)
|
|
6556
5989
|
|
|
6557
|
-
await validateKustomizationJson6902NoRemoveAddSamePath(root, yamlFiles, fail)
|
|
6558
|
-
|
|
6559
5990
|
await validateKustomizationPathRefsExistOnDisk(root, yamlFiles, fail)
|
|
6560
5991
|
|
|
6561
|
-
await validateKustomizationResourcesSortedAlphabetically(root, yamlFiles, fail)
|
|
6562
|
-
|
|
6563
|
-
await validateKustomizationPatchesStructuralSort(root, yamlFiles, fail)
|
|
6564
|
-
|
|
6565
5992
|
await validateKustomizationPatchTargetsResolved(root, yamlFiles, fail)
|
|
6566
5993
|
|
|
6567
5994
|
await validateKustomizeHpaPdbOnlyWithBaseDeployment(root, yamlFiles, fail, pass)
|
|
6568
5995
|
|
|
6569
|
-
await ensureBaseKustomizationHasNamespace(root, yamlFiles, fail)
|
|
6570
|
-
|
|
6571
5996
|
await validateConfigMapNameMatchesDeployment(root, yamlFiles, fail, pass)
|
|
6572
5997
|
|
|
6573
5998
|
await validateHasuraConfigMapRemoteSchemaPermissions(root, yamlFiles, fail, pass)
|