@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
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
# Порт
|
|
1
|
+
# Порт структурної перевірки `HealthCheckPolicy` з
|
|
2
2
|
# `npm/scripts/check-abie.mjs` (abie.mdc).
|
|
3
3
|
#
|
|
4
4
|
# Запуск (локально):
|
|
5
|
-
# conftest test path/to/k8s/.../hc.yaml
|
|
5
|
+
# conftest test path/to/k8s/.../hc.yaml \
|
|
6
|
+
# -p npm/policy/abie/health_check_policy \
|
|
6
7
|
# --namespace abie.health_check_policy
|
|
7
8
|
#
|
|
8
|
-
#
|
|
9
|
-
# `networking.gke.io/v1`)
|
|
10
|
-
# - `
|
|
11
|
-
# - `spec.config.
|
|
12
|
-
# - `spec.
|
|
9
|
+
# Перевіряє для `kind: HealthCheckPolicy`:
|
|
10
|
+
# - `apiVersion: networking.gke.io/v1` (точна відповідність);
|
|
11
|
+
# - `metadata.name` — непорожній рядок;
|
|
12
|
+
# - `spec.default.config.type: HTTP`;
|
|
13
|
+
# - `spec.default.config.httpHealthCheck.requestPath` — непорожній і
|
|
14
|
+
# починається з `/`;
|
|
15
|
+
# - `spec.default.config.httpHealthCheck.port: 8080`;
|
|
16
|
+
# - `spec.targetRef.kind: Service`;
|
|
17
|
+
# - `spec.targetRef.name` має суфікс `-hl` (headless backend).
|
|
13
18
|
#
|
|
14
|
-
# Cross-file gating (`abie`
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# (
|
|
18
|
-
# обчислений `<name>-hl` суфікс); ця Rego — швидкий gate для одиничного YAML
|
|
19
|
-
# (наприклад через IDE).
|
|
19
|
+
# Cross-file gating (правило `abie` у `.n-cursor.json`, парність з Deployment-каталогу,
|
|
20
|
+
# точна звірка `targetRef.name` з обчисленим `<deployment.name>-hl`) — у JS
|
|
21
|
+
# (`check-abie.mjs`: `validateAbieHcPolicy`, `checkHcYamlFiles`). JS authoritative;
|
|
22
|
+
# ця Rego — швидкий gate для одиничного YAML (наприклад через IDE).
|
|
20
23
|
#
|
|
21
24
|
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
22
25
|
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
@@ -25,21 +28,58 @@ package abie.health_check_policy
|
|
|
25
28
|
|
|
26
29
|
import rego.v1
|
|
27
30
|
|
|
31
|
+
expected_api_version := "networking.gke.io/v1"
|
|
32
|
+
|
|
28
33
|
req_path_starts_with_slash_template := concat(" ", [
|
|
29
34
|
"HealthCheckPolicy: requestPath має починатись з `/`",
|
|
30
35
|
"(зараз %q) (abie.mdc)",
|
|
31
36
|
])
|
|
32
37
|
|
|
33
|
-
|
|
38
|
+
target_ref_name_template := concat(" ", [
|
|
39
|
+
"HealthCheckPolicy: targetRef.name має посилатися на headless Service",
|
|
40
|
+
"(очікується %q, суфікс -hl) (зараз %q) (abie.mdc)",
|
|
41
|
+
])
|
|
42
|
+
|
|
43
|
+
# ── deny: apiVersion / kind ───────────────────────────────────────────────
|
|
34
44
|
|
|
35
45
|
deny contains msg if {
|
|
46
|
+
input.kind == "HealthCheckPolicy"
|
|
47
|
+
api_version := object.get(input, "apiVersion", "")
|
|
48
|
+
api_version != expected_api_version
|
|
49
|
+
msg := sprintf(
|
|
50
|
+
"HealthCheckPolicy: apiVersion має бути %q (зараз %q) (abie.mdc)",
|
|
51
|
+
[expected_api_version, api_version],
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# ── deny: metadata.name ───────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
deny contains "HealthCheckPolicy: metadata.name має бути непорожнім рядком (abie.mdc)" if {
|
|
58
|
+
input.kind == "HealthCheckPolicy"
|
|
59
|
+
startswith(object.get(input, "apiVersion", ""), "networking.gke.io/")
|
|
60
|
+
name := object.get(object.get(input, "metadata", {}), "name", "")
|
|
61
|
+
trim_space(name) == ""
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# ── deny: spec.default.config.type ────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
deny contains "HealthCheckPolicy: spec.default.config.type має бути HTTP (abie.mdc)" if {
|
|
36
67
|
is_health_check_policy
|
|
68
|
+
is_object(default_config)
|
|
69
|
+
object.get(default_config, "type", "") != "HTTP"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# ── deny: requestPath ─────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
deny contains "HealthCheckPolicy: spec.default.config.httpHealthCheck.requestPath має бути непорожнім (abie.mdc)" if {
|
|
75
|
+
is_health_check_policy
|
|
76
|
+
is_object(http_health_check)
|
|
37
77
|
req_path == ""
|
|
38
|
-
msg := "HealthCheckPolicy: spec.config.httpHealthCheck.requestPath має бути непорожнім (abie.mdc)"
|
|
39
78
|
}
|
|
40
79
|
|
|
41
80
|
deny contains msg if {
|
|
42
81
|
is_health_check_policy
|
|
82
|
+
is_object(http_health_check)
|
|
43
83
|
req_path != ""
|
|
44
84
|
not startswith(req_path, "/")
|
|
45
85
|
msg := sprintf(req_path_starts_with_slash_template, [req_path])
|
|
@@ -49,29 +89,58 @@ deny contains msg if {
|
|
|
49
89
|
|
|
50
90
|
deny contains msg if {
|
|
51
91
|
is_health_check_policy
|
|
92
|
+
is_object(http_health_check)
|
|
52
93
|
port := object.get(http_health_check, "port", null)
|
|
53
94
|
port != null
|
|
54
95
|
port != 8080
|
|
55
96
|
msg := sprintf("HealthCheckPolicy: port має бути 8080 (зараз %v) (abie.mdc)", [port])
|
|
56
97
|
}
|
|
57
98
|
|
|
58
|
-
# ── deny: targetRef.
|
|
99
|
+
# ── deny: targetRef.kind == Service ──────────────────────────────────────
|
|
59
100
|
|
|
60
101
|
deny contains msg if {
|
|
61
102
|
is_health_check_policy
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
103
|
+
target_ref := object.get(object.get(input, "spec", {}), "targetRef", null)
|
|
104
|
+
is_object(target_ref)
|
|
105
|
+
kind := object.get(target_ref, "kind", "")
|
|
106
|
+
kind != ""
|
|
107
|
+
kind != "Service"
|
|
108
|
+
msg := sprintf("HealthCheckPolicy: targetRef.kind має бути Service (зараз %q) (abie.mdc)", [kind])
|
|
66
109
|
}
|
|
67
110
|
|
|
68
|
-
# ──
|
|
111
|
+
# ── deny: targetRef.name = `<hcp.metadata.name>-hl` (exact, з нормалізацією)
|
|
112
|
+
|
|
113
|
+
deny contains msg if {
|
|
114
|
+
is_health_check_policy
|
|
115
|
+
hcp_name := object.get(object.get(input, "metadata", {}), "name", "")
|
|
116
|
+
hcp_name != ""
|
|
117
|
+
target_name := object.get(object.get(object.get(input, "spec", {}), "targetRef", {}), "name", "")
|
|
118
|
+
target_name != ""
|
|
119
|
+
expected_hl := expected_target_ref_name(hcp_name)
|
|
120
|
+
target_name != expected_hl
|
|
121
|
+
msg := sprintf(target_ref_name_template, [expected_hl, target_name])
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# Нормалізація: якщо `metadata.name` уже закінчується на `-hl` — використовуємо
|
|
125
|
+
# як є; інакше додаємо суфікс. Узгоджено з `validateAbieHcPolicy`
|
|
126
|
+
# у `check-abie.mjs`.
|
|
127
|
+
expected_target_ref_name(name) := name if {
|
|
128
|
+
endswith(name, "-hl")
|
|
129
|
+
} else := concat("", [name, "-hl"])
|
|
130
|
+
|
|
131
|
+
# ── helpers ───────────────────────────────────────────────────────────────
|
|
69
132
|
|
|
70
133
|
is_health_check_policy if {
|
|
71
134
|
input.kind == "HealthCheckPolicy"
|
|
72
135
|
startswith(object.get(input, "apiVersion", ""), "networking.gke.io/")
|
|
73
136
|
}
|
|
74
137
|
|
|
75
|
-
|
|
138
|
+
default_config := object.get(
|
|
139
|
+
object.get(object.get(input, "spec", {}), "default", {}),
|
|
140
|
+
"config",
|
|
141
|
+
{},
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
http_health_check := object.get(default_config, "httpHealthCheck", {})
|
|
76
145
|
|
|
77
146
|
req_path := object.get(http_health_check, "requestPath", "")
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Тести для `abie.health_check_policy`. Запуск:
|
|
2
|
+
# conftest verify -p npm/policy/abie/health_check_policy
|
|
3
|
+
package abie.health_check_policy_test
|
|
4
|
+
|
|
5
|
+
import rego.v1
|
|
6
|
+
|
|
7
|
+
import data.abie.health_check_policy
|
|
8
|
+
|
|
9
|
+
valid_hcp := {
|
|
10
|
+
"apiVersion": "networking.gke.io/v1",
|
|
11
|
+
"kind": "HealthCheckPolicy",
|
|
12
|
+
"metadata": {"name": "api"},
|
|
13
|
+
"spec": {
|
|
14
|
+
"default": {"config": {
|
|
15
|
+
"type": "HTTP",
|
|
16
|
+
"httpHealthCheck": {"requestPath": "/healthz", "port": 8080},
|
|
17
|
+
}},
|
|
18
|
+
"targetRef": {"group": "", "kind": "Service", "name": "api-hl"},
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# ── happy path ────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
test_allow_canonical if {
|
|
25
|
+
count(health_check_policy.deny) == 0 with input as valid_hcp
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# ── apiVersion ────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
test_deny_wrong_api_version if {
|
|
31
|
+
bad := json.patch(valid_hcp, [{"op": "replace", "path": "/apiVersion", "value": "networking.gke.io/v1beta1"}])
|
|
32
|
+
count(health_check_policy.deny) > 0 with input as bad
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# ── metadata.name ─────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
test_deny_empty_name if {
|
|
38
|
+
bad := json.patch(valid_hcp, [{"op": "replace", "path": "/metadata/name", "value": ""}])
|
|
39
|
+
count(health_check_policy.deny) > 0 with input as bad
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# ── spec.default.config.type ──────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
test_deny_config_type_not_http if {
|
|
45
|
+
bad := json.patch(valid_hcp, [{"op": "replace", "path": "/spec/default/config/type", "value": "TCP"}])
|
|
46
|
+
count(health_check_policy.deny) > 0 with input as bad
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# ── requestPath ───────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
test_deny_empty_request_path if {
|
|
52
|
+
bad := json.patch(valid_hcp, [{
|
|
53
|
+
"op": "replace",
|
|
54
|
+
"path": "/spec/default/config/httpHealthCheck/requestPath",
|
|
55
|
+
"value": "",
|
|
56
|
+
}])
|
|
57
|
+
count(health_check_policy.deny) > 0 with input as bad
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
test_deny_request_path_without_slash if {
|
|
61
|
+
bad := json.patch(valid_hcp, [{
|
|
62
|
+
"op": "replace",
|
|
63
|
+
"path": "/spec/default/config/httpHealthCheck/requestPath",
|
|
64
|
+
"value": "healthz",
|
|
65
|
+
}])
|
|
66
|
+
count(health_check_policy.deny) > 0 with input as bad
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# ── port ──────────────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
test_deny_port_not_8080 if {
|
|
72
|
+
bad := json.patch(valid_hcp, [{
|
|
73
|
+
"op": "replace",
|
|
74
|
+
"path": "/spec/default/config/httpHealthCheck/port",
|
|
75
|
+
"value": 9090,
|
|
76
|
+
}])
|
|
77
|
+
count(health_check_policy.deny) > 0 with input as bad
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# ── targetRef ─────────────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
test_deny_target_ref_kind_not_service if {
|
|
83
|
+
bad := json.patch(valid_hcp, [{"op": "replace", "path": "/spec/targetRef/kind", "value": "Gateway"}])
|
|
84
|
+
count(health_check_policy.deny) > 0 with input as bad
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
test_deny_target_ref_name_without_hl if {
|
|
88
|
+
bad := json.patch(valid_hcp, [{"op": "replace", "path": "/spec/targetRef/name", "value": "api"}])
|
|
89
|
+
count(health_check_policy.deny) > 0 with input as bad
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# Не HCP — пакет не діє.
|
|
93
|
+
test_allow_other_kind if {
|
|
94
|
+
count(health_check_policy.deny) == 0 with input as {
|
|
95
|
+
"apiVersion": "v1",
|
|
96
|
+
"kind": "ConfigMap",
|
|
97
|
+
"metadata": {"name": "x"},
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Тести для `abie.http_route_base`. Запуск:
|
|
2
|
+
# conftest verify -p npm/policy/abie/http_route_base
|
|
3
|
+
package abie.http_route_base_test
|
|
4
|
+
|
|
5
|
+
import rego.v1
|
|
6
|
+
|
|
7
|
+
import data.abie.http_route_base
|
|
8
|
+
|
|
9
|
+
mk_route(hostnames) := {
|
|
10
|
+
"apiVersion": "gateway.networking.k8s.io/v1",
|
|
11
|
+
"kind": "HTTPRoute",
|
|
12
|
+
"metadata": {"name": "r", "namespace": "dev"},
|
|
13
|
+
"spec": {"hostnames": hostnames},
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
# ── allow ────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
test_allow_apex if {
|
|
19
|
+
count(http_route_base.deny) == 0 with input as mk_route(["aiml.live"])
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test_allow_subdomain if {
|
|
23
|
+
count(http_route_base.deny) == 0 with input as mk_route(["api.aiml.live"])
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
test_allow_wildcard if {
|
|
27
|
+
count(http_route_base.deny) == 0 with input as mk_route(["*.aiml.live"])
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test_allow_uppercase_apex if {
|
|
31
|
+
count(http_route_base.deny) == 0 with input as mk_route(["AIML.LIVE"])
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
test_allow_multiple_subdomains if {
|
|
35
|
+
count(http_route_base.deny) == 0 with input as mk_route(["api.aiml.live", "admin.aiml.live"])
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# ── deny ─────────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
test_deny_other_apex if {
|
|
41
|
+
count(http_route_base.deny) > 0 with input as mk_route(["example.com"])
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
test_deny_wrong_subdomain if {
|
|
45
|
+
count(http_route_base.deny) > 0 with input as mk_route(["api.example.com"])
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
test_deny_mixed_one_bad if {
|
|
49
|
+
count(http_route_base.deny) > 0 with input as mk_route(["api.aiml.live", "evil.com"])
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
test_deny_aiml_live_substring if {
|
|
53
|
+
# "aiml.live.example.com" не має закінчуватись на ".aiml.live" — це інший домен.
|
|
54
|
+
count(http_route_base.deny) > 0 with input as mk_route(["aiml.live.example.com"])
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Не HTTPRoute — пакет не діє.
|
|
58
|
+
test_allow_non_httproute if {
|
|
59
|
+
count(http_route_base.deny) == 0 with input as {
|
|
60
|
+
"apiVersion": "v1",
|
|
61
|
+
"kind": "Service",
|
|
62
|
+
"metadata": {"name": "x"},
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -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` (з тегом
|