@nitra/cursor 1.8.56 → 1.8.58
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/mdc/k8s.mdc +11 -0
- package/package.json +1 -1
- package/scripts/check-k8s.mjs +135 -2
package/mdc/k8s.mdc
CHANGED
|
@@ -106,6 +106,17 @@ resources: {}
|
|
|
106
106
|
|
|
107
107
|
У кожному контейнері **`Deployment`** має бути **`imagePullPolicy: Always`** (див. **`check k8s`**).
|
|
108
108
|
|
|
109
|
+
Якщо в **`Deployment`** у **`spec.template.spec.containers`** або **`initContainers`** задано образ **`hasura/graphql-engine`**, у полі **`image`** має бути саме **`hasura/graphql-engine:v2.48.15.ubi.amd64`** (для еквівалента Docker Hub допускається префікс **`docker.io/`**). Інші теги або сторонні реєстри з тим самим репозиторієм **`hasura/graphql-engine`** — порушення **`check k8s`**.
|
|
110
|
+
|
|
111
|
+
## Service: заборонені анотації GKE
|
|
112
|
+
|
|
113
|
+
У **`kind: Service`** не використовуй у **`metadata.annotations`** ключі:
|
|
114
|
+
|
|
115
|
+
- **`cloud.google.com/neg`**
|
|
116
|
+
- **`cloud.google.com/backend-config`**
|
|
117
|
+
|
|
118
|
+
Вони потрібні для інтеграції з **Ingress** / класичним балансуванням GKE; після переходу на **Gateway API** їх слід **прибрати** з маніфестів. **`check k8s`** завершиться помилкою, якщо хоча б один із цих ключів присутній.
|
|
119
|
+
|
|
109
120
|
## Kustomize: структура каталогів (`base` / overlays)
|
|
110
121
|
|
|
111
122
|
Трансформуй дерева **`**/k8s`**, щоб **винести спільне** через [Kustomize](https://kustomize.io/): один канонічний **`base`** і тонкі **overlays** для інших середовищ.
|
package/package.json
CHANGED
package/scripts/check-k8s.mjs
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Додатково: у кожному YAML-документі з **`kind: Deployment`** у кожного контейнера
|
|
9
9
|
* **`spec.template.spec.containers[]`** має бути ключ **`resources`** (значення — об'єкт, допускається
|
|
10
|
-
* порожній **`{}`**) та **`imagePullPolicy: Always`**.
|
|
10
|
+
* порожній **`{}`**) та **`imagePullPolicy: Always`**. Якщо серед **`containers`** / **`initContainers`**
|
|
11
|
+
* є образ **`hasura/graphql-engine`**, дозволено лише пін **`HASURA_GRAPHQL_ENGINE_IMAGE`** (див. k8s.mdc).
|
|
11
12
|
*
|
|
12
13
|
* **Namespace і Kustomize:** YAML у **`…/k8s/base/`** (окрім імені **`kustomization.yaml`**)
|
|
13
14
|
* завжди має **непорожній** **`metadata.namespace`** у відповідних документах (узгоджено з dev у репозиторії),
|
|
@@ -18,6 +19,9 @@
|
|
|
18
19
|
*
|
|
19
20
|
* **`kind: Ingress`** заборонено (потрібен перехід на Gateway API).
|
|
20
21
|
*
|
|
22
|
+
* У **`kind: Service`** у **`metadata.annotations`** не повинно бути ключів **`cloud.google.com/neg`**
|
|
23
|
+
* та **`cloud.google.com/backend-config`** (див. k8s.mdc).
|
|
24
|
+
*
|
|
21
25
|
* Структура **Kustomize** (див. k8s.mdc): заборона шляхів **`…/k8s/dev/…`**; у **`k8s/base/kustomization.yaml`**
|
|
22
26
|
* завжди має бути непорожнє поле **`namespace:`** (перевірка, якщо файл існує).
|
|
23
27
|
*
|
|
@@ -38,6 +42,27 @@ import { walkDir } from './utils/walkDir.mjs'
|
|
|
38
42
|
/** Версія набору схем yannh — узгоджено з k8s.mdc */
|
|
39
43
|
const YANNH_PIN = 'v1.33.9-standalone-strict'
|
|
40
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Дозволений образ **hasura/graphql-engine** у Deployment (узгоджено з k8s.mdc).
|
|
47
|
+
* Еквівалент **`docker.io/…`** також приймається.
|
|
48
|
+
*/
|
|
49
|
+
export const HASURA_GRAPHQL_ENGINE_IMAGE = 'hasura/graphql-engine:v2.48.15.ubi.amd64'
|
|
50
|
+
|
|
51
|
+
/** Набір прийнятних рядків `image` без digest (`@sha256:…`). */
|
|
52
|
+
const HASURA_GRAPHQL_ENGINE_ALLOWED_IMAGES = new Set([
|
|
53
|
+
HASURA_GRAPHQL_ENGINE_IMAGE,
|
|
54
|
+
`docker.io/${HASURA_GRAPHQL_ENGINE_IMAGE}`
|
|
55
|
+
])
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Ключі анотацій GKE (NEG / BackendConfig) у **Service** — заборонені (узгоджено з k8s.mdc).
|
|
59
|
+
* @type {readonly string[]}
|
|
60
|
+
*/
|
|
61
|
+
export const SERVICE_FORBIDDEN_GCP_ANNOTATION_KEYS = Object.freeze([
|
|
62
|
+
'cloud.google.com/neg',
|
|
63
|
+
'cloud.google.com/backend-config'
|
|
64
|
+
])
|
|
65
|
+
|
|
41
66
|
/** Гілка репозиторію yannh/kubernetes-json-schema для raw.githubusercontent.com (каталог набору в URL одразу після ref). */
|
|
42
67
|
const YANNH_REF = 'master'
|
|
43
68
|
|
|
@@ -570,6 +595,105 @@ export function deploymentImagePullPolicyViolation(manifest) {
|
|
|
570
595
|
return null
|
|
571
596
|
}
|
|
572
597
|
|
|
598
|
+
/**
|
|
599
|
+
* Прибирає digest з посилання на образ (`@sha256:…`) для порівняння тега.
|
|
600
|
+
* @param {string} image значення поля `image`
|
|
601
|
+
* @returns {string} той самий рядок без суфікса `@…` (digest), з `.trim()`
|
|
602
|
+
*/
|
|
603
|
+
function stripImageDigest(image) {
|
|
604
|
+
const at = image.indexOf('@')
|
|
605
|
+
return (at === -1 ? image : image.slice(0, at)).trim()
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Чи рядок `image` вказує на репозиторій **hasura/graphql-engine** (будь-який тег / без тега).
|
|
610
|
+
* @param {string} image значення поля `image`
|
|
611
|
+
* @returns {boolean} true, якщо шлях образу закінчується на `hasura/graphql-engine` з тегом або без
|
|
612
|
+
*/
|
|
613
|
+
function isHasuraGraphqlEngineImageRef(image) {
|
|
614
|
+
const s = stripImageDigest(image)
|
|
615
|
+
return /(^|\/)hasura\/graphql-engine(?:[:]|$)/u.test(s)
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Перевіряє пін образу Hasura у одному списку контейнерів Pod spec.
|
|
620
|
+
* @param {string} list ім’я поля для повідомлення (`containers` / `initContainers`)
|
|
621
|
+
* @param {unknown} containers значення з маніфесту
|
|
622
|
+
* @returns {string | null} текст порушення або null
|
|
623
|
+
*/
|
|
624
|
+
function hasuraGraphqlEngineViolationInContainerList(list, containers) {
|
|
625
|
+
if (!Array.isArray(containers)) return null
|
|
626
|
+
for (const [i, c] of containers.entries()) {
|
|
627
|
+
const label =
|
|
628
|
+
typeof c === 'object' && c !== null && !Array.isArray(c) && typeof c.name === 'string' && c.name !== ''
|
|
629
|
+
? c.name
|
|
630
|
+
: `#${i + 1}`
|
|
631
|
+
if (c !== null && c !== undefined && typeof c === 'object' && !Array.isArray(c)) {
|
|
632
|
+
const cont = /** @type {Record<string, unknown>} */ (c)
|
|
633
|
+
const image = cont.image
|
|
634
|
+
if (typeof image === 'string' && image.trim() !== '' && isHasuraGraphqlEngineImageRef(image)) {
|
|
635
|
+
const normalized = stripImageDigest(image)
|
|
636
|
+
if (!HASURA_GRAPHQL_ENGINE_ALLOWED_IMAGES.has(normalized)) {
|
|
637
|
+
return `${list} "${label}": образ hasura/graphql-engine має бути ${HASURA_GRAPHQL_ENGINE_IMAGE} (зараз: ${image}) (див. k8s.mdc)`
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return null
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Чи порушує **Deployment** вимогу пінованого образу **hasura/graphql-engine** (k8s.mdc).
|
|
647
|
+
* @param {unknown} manifest корінь YAML-документа
|
|
648
|
+
* @returns {string | null} текст порушення або null, якщо не Deployment / образу немає / ок
|
|
649
|
+
*/
|
|
650
|
+
export function deploymentHasuraGraphqlEngineImageViolation(manifest) {
|
|
651
|
+
if (manifest === null || manifest === undefined || typeof manifest !== 'object' || Array.isArray(manifest))
|
|
652
|
+
return null
|
|
653
|
+
const rec = /** @type {Record<string, unknown>} */ (manifest)
|
|
654
|
+
if (rec.kind !== 'Deployment') return null
|
|
655
|
+
const spec = rec.spec
|
|
656
|
+
if (spec === null || spec === undefined || typeof spec !== 'object' || Array.isArray(spec)) return null
|
|
657
|
+
const template = /** @type {Record<string, unknown>} */ (spec).template
|
|
658
|
+
if (template === null || template === undefined || typeof template !== 'object' || Array.isArray(template))
|
|
659
|
+
return null
|
|
660
|
+
const podSpecRaw = /** @type {Record<string, unknown>} */ (template).spec
|
|
661
|
+
if (podSpecRaw === null || podSpecRaw === undefined || typeof podSpecRaw !== 'object' || Array.isArray(podSpecRaw))
|
|
662
|
+
return null
|
|
663
|
+
const podSpec = /** @type {Record<string, unknown>} */ (podSpecRaw)
|
|
664
|
+
|
|
665
|
+
const main = hasuraGraphqlEngineViolationInContainerList('containers', podSpec.containers)
|
|
666
|
+
if (main !== null) return main
|
|
667
|
+
return hasuraGraphqlEngineViolationInContainerList('initContainers', podSpec.initContainers)
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Чи **Service** містить заборонені анотації GKE у **`metadata.annotations`** (k8s.mdc).
|
|
672
|
+
* @param {unknown} manifest корінь YAML-документа
|
|
673
|
+
* @returns {string | null} текст порушення або null, якщо не Service / анотацій немає / ок
|
|
674
|
+
*/
|
|
675
|
+
export function serviceForbiddenGcpAnnotationsViolation(manifest) {
|
|
676
|
+
if (manifest === null || manifest === undefined || typeof manifest !== 'object' || Array.isArray(manifest))
|
|
677
|
+
return null
|
|
678
|
+
const rec = /** @type {Record<string, unknown>} */ (manifest)
|
|
679
|
+
if (rec.kind !== 'Service') return null
|
|
680
|
+
const meta = rec.metadata
|
|
681
|
+
if (meta === null || meta === undefined || typeof meta !== 'object' || Array.isArray(meta)) return null
|
|
682
|
+
const m = /** @type {Record<string, unknown>} */ (meta)
|
|
683
|
+
const ann = m.annotations
|
|
684
|
+
if (ann === null || ann === undefined || typeof ann !== 'object' || Array.isArray(ann)) return null
|
|
685
|
+
const a = /** @type {Record<string, unknown>} */ (ann)
|
|
686
|
+
/** @type {string[]} */
|
|
687
|
+
const found = []
|
|
688
|
+
for (const key of SERVICE_FORBIDDEN_GCP_ANNOTATION_KEYS) {
|
|
689
|
+
if (Object.hasOwn(a, key)) {
|
|
690
|
+
found.push(key)
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
if (found.length === 0) return null
|
|
694
|
+
return `metadata.annotations: прибери заборонені ключі GKE: ${found.join(', ')} (див. k8s.mdc)`
|
|
695
|
+
}
|
|
696
|
+
|
|
573
697
|
/**
|
|
574
698
|
* Для маніфестів, **підключених** до Kustomize (шлях у `resources` / `patches` / …), **metadata.namespace** не додають.
|
|
575
699
|
* @param {unknown} manifest корінь YAML-документа
|
|
@@ -638,7 +762,8 @@ export function isK8sBaseManifestYamlPath(rel, baseLower) {
|
|
|
638
762
|
}
|
|
639
763
|
|
|
640
764
|
/**
|
|
641
|
-
* Парсить усі YAML-документи: **metadata.namespace**, **Deployment.resources**, **imagePullPolicy
|
|
765
|
+
* Парсить усі YAML-документи: **metadata.namespace**, **Deployment.resources**, **imagePullPolicy**, **Hasura image pin**,
|
|
766
|
+
* **Service — заборонені GKE-анотації**.
|
|
642
767
|
* @param {string} rel відносний шлях
|
|
643
768
|
* @param {string} baseLower basename файлу (нижній регістр)
|
|
644
769
|
* @param {string} body вміст після modeline
|
|
@@ -690,6 +815,14 @@ function validateK8sYamlPolicyDocuments(rel, baseLower, body, fail, kustomizeMan
|
|
|
690
815
|
if (pullV !== null) {
|
|
691
816
|
fail(`${rel}: Deployment (документ ${di + 1}): ${pullV}`)
|
|
692
817
|
}
|
|
818
|
+
const hasuraV = deploymentHasuraGraphqlEngineImageViolation(obj)
|
|
819
|
+
if (hasuraV !== null) {
|
|
820
|
+
fail(`${rel}: Deployment (документ ${di + 1}): ${hasuraV}`)
|
|
821
|
+
}
|
|
822
|
+
const svcGcpV = serviceForbiddenGcpAnnotationsViolation(obj)
|
|
823
|
+
if (svcGcpV !== null) {
|
|
824
|
+
fail(`${rel}: Service (документ ${di + 1}): ${svcGcpV}`)
|
|
825
|
+
}
|
|
693
826
|
}
|
|
694
827
|
}
|
|
695
828
|
}
|