@7n/rules-ci-github 1.3.1 → 1.4.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 +10 -0
- package/package.json +1 -1
- package/rules/ga/lint_repo_yml/concern.json +12 -0
- package/rules/ga/lint_repo_yml/lint_repo_yml.mdc +9 -0
- package/rules/ga/lint_repo_yml/template/lint-repo.yml.snippet.yml +31 -0
- package/rules/ga/main.mdc +10 -1
- package/rules/ga/service_deploy_workflow/concern.json +9 -0
- package/rules/ga/service_deploy_workflow/service_deploy_workflow.mdc +21 -0
- package/rules/ga/service_deploy_workflow/service_deploy_workflow.rego +213 -0
- package/rules/ga/service_deploy_workflow/template/deploy-service.yml.snippet.yml +88 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.0] - 2026-07-18
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- сервіс-орієнтований CI-канон: концерн ga/service_deploy_workflow (форма `deploy-<service>.yml`: plan-гейт ci plan, per-domain lint-джоби з needs+if, deploy dependsOn всі перевірки) і ga/lint_repo_yml (окремий lint-repo.yml для repo-wide перевірок, що не гейтять деплой)
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- ga/workflows: додано пропущені `await` для `runConftestBatch` (стала async у ядрі @7n/rules 1.14) — детектор падав із «violations is not iterable»
|
|
12
|
+
|
|
3
13
|
## [1.3.1] - 2026-07-18
|
|
4
14
|
|
|
5
15
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@7n/rules-ci-github",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "\u041f\u043b\u0430\u0433\u0456\u043d @7n/rules: \u043a\u0430\u043d\u043e\u043d GitHub Actions (.github/workflows) \u2014 \u043e\u0431\u043e\u0432'\u044f\u0437\u043a\u043e\u0432\u0456 workflow, zizmor, VS Code",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"github-actions",
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
|
|
3
|
+
"fixability": "config",
|
|
4
|
+
"policy": {
|
|
5
|
+
"files": {
|
|
6
|
+
"single": ".github/workflows/lint-repo.yml",
|
|
7
|
+
"required": true
|
|
8
|
+
},
|
|
9
|
+
"check": "template",
|
|
10
|
+
"missingMessage": ".github/workflows/lint-repo.yml не існує — repo-wide перевірки без path-підтримки (knip/jscpd/dep-policy) живуть в окремому workflow, що не гейтить деплой; створи за каноном ga.mdc (mixin @7n/rules-ci-github)"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
## Deep-subset `.github/workflows/lint-repo.yml` (repo-wide перевірки)
|
|
2
|
+
|
|
3
|
+
Цільовий файл: `.github/workflows/lint-repo.yml` — **обовʼязковий** для github-консюмерів.
|
|
4
|
+
|
|
5
|
+
Repo-wide перевірки без path-підтримки (`scope: full` без glob: knip, jscpd, dep-policy, utils_imports тощо) не вміють скоупитись на каталог сервісу, тому живуть в окремому workflow з кроком `bunx n-rules lint --repo-wide --no-fix` і **не гейтять деплой**: жоден `deploy-*.yml` не має `needs` на цей workflow (сервіс-канон — `service_deploy_workflow`).
|
|
6
|
+
|
|
7
|
+
Перевірка — чистий deep-subset без rego (`"check": "template"`): фактичний файл мусить структурно містити сніпет (тригер push/PR на dev/main без paths-фільтра, checkout з `persist-credentials: false`, prep `./.github/actions/setup-bun-deps`, крок `lint --repo-wide --no-fix`). Додаткові кроки/поля дозволені.
|
|
8
|
+
|
|
9
|
+
Канон-snippet: [lint-repo.yml.snippet.yml](./template/lint-repo.yml.snippet.yml)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Lint repo-wide
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- dev
|
|
7
|
+
- main
|
|
8
|
+
|
|
9
|
+
pull_request:
|
|
10
|
+
branches:
|
|
11
|
+
- dev
|
|
12
|
+
- main
|
|
13
|
+
|
|
14
|
+
concurrency:
|
|
15
|
+
group: ${{ github.ref }}-${{ github.workflow }}
|
|
16
|
+
cancel-in-progress: true
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
lint-repo:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v6
|
|
25
|
+
with:
|
|
26
|
+
persist-credentials: false
|
|
27
|
+
|
|
28
|
+
- uses: ./.github/actions/setup-bun-deps
|
|
29
|
+
|
|
30
|
+
- name: Repo-wide lint
|
|
31
|
+
run: bunx n-rules lint --repo-wide --no-fix
|
package/rules/ga/main.mdc
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Правила форматів для .github/workflows
|
|
3
|
-
version: '1.
|
|
3
|
+
version: '1.11'
|
|
4
4
|
globs: ".github/workflows/*.yml"
|
|
5
5
|
alwaysApply: false
|
|
6
6
|
---
|
|
@@ -15,3 +15,12 @@ alwaysApply: false
|
|
|
15
15
|
- [clean-merged-branch.yml.snippet.yml](./clean_merged_branch/template/clean-merged-branch.yml.snippet.yml)
|
|
16
16
|
- [extensions.json.snippet.json](./vscode_extensions/template/extensions.json.snippet.json)
|
|
17
17
|
- [uses-min-versions.snippet.json](./workflow_common/template/uses-min-versions.snippet.json)
|
|
18
|
+
- [lint-repo.yml.snippet.yml](./lint_repo_yml/template/lint-repo.yml.snippet.yml)
|
|
19
|
+
|
|
20
|
+
## Repo-wide лінти (`lint-repo.yml`, обовʼязковий)
|
|
21
|
+
|
|
22
|
+
Перевірки без path-підтримки (`scope: full` без glob: knip, jscpd, dep-policy тощо) живуть в окремому обовʼязковому workflow `.github/workflows/lint-repo.yml` із кроком `bunx n-rules lint --repo-wide --no-fix`. Він **не гейтить деплой** — жоден deploy-workflow не має на нього `needs`. Деталі: [lint_repo_yml.mdc](./lint_repo_yml/lint_repo_yml.mdc).
|
|
23
|
+
|
|
24
|
+
## Сервіс-орієнтовані deploy-workflow (`deploy-*.yml`, опційно)
|
|
25
|
+
|
|
26
|
+
Монорепо з деплой-сервісами (каталог сервісу, напр. `run/nexus`) оформлює деплой per-service workflow `deploy-<service>.yml`: тригер `paths` по каталогу → джоба `plan` (`bunx n-rules ci plan --path <svc> --github`) → паралельні `lint-<domain>`-джоби з гейтом по outputs плану + тести сервісу → deploy з `needs` на всі перевірки (skipped не блокує, failed блокує). Форму перевіряє rego-концерн `service_deploy_workflow`; приклад — [deploy-service.yml.snippet.yml](./service_deploy_workflow/template/deploy-service.yml.snippet.yml), деталі — [service_deploy_workflow.mdc](./service_deploy_workflow/service_deploy_workflow.mdc). Співіснує з file-type-workflow (`lint-js.yml` тощо) — ті покривають зміни поза сервіс-каталогами.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
## Rego-gate сервіс-орієнтованих deploy-workflow (`.github/workflows/deploy-*.yml`)
|
|
2
|
+
|
|
3
|
+
Rego-пакет: `ga.service_deploy_workflow`
|
|
4
|
+
|
|
5
|
+
Цільові файли: усі `.github/workflows/deploy-*.yml` (walkGlob). Немає жодного — концерн мовчить: сервіс-канон опційний, перелік сервісів консюмер-специфічний (реєстру немає).
|
|
6
|
+
|
|
7
|
+
Канонічна форма per-service workflow (`deploy-<service>.yml`, каталог сервісу — напр. `run/nexus`):
|
|
8
|
+
|
|
9
|
+
1. **Тригер** — `on.push.paths` із глобом каталогу сервісу (`run/<service>/**`).
|
|
10
|
+
2. **`plan`-джоба** — checkout `fetch-depth: 0` → prep (`./.github/actions/setup-bun-deps`) → `bunx n-rules ci plan --path run/<service> --github` (крок з `id: plan`, outputs прокинуті в `jobs.plan.outputs`).
|
|
11
|
+
3. **`lint-<domain>`-джоби** (паралельно, по одній на домен сервісу) — `needs: plan`, гейт `if: needs.plan.outputs.<domain> == 'true'` (ключ = домен із `-`→`_`), крок `bunx n-rules lint <domain> --path run/<service> --no-fix` з тим самим `--path`, що в plan. Зайва домен-джоба нешкідлива (ci plan її скіпне), відсутня потрібна — дірка в гейті.
|
|
12
|
+
4. **`test`-джоба** — `needs: plan`, `if: needs.plan.outputs.any == 'true'`; тести лише сервісу (`bun test run/<service>`; python-стек — `astral-sh/setup-uv` + `uv sync --locked` + `pytest run/<service>`).
|
|
13
|
+
5. **Деплой** — needs-ланцюг (можна через проміжний `build`) **транзитивно досягає** `plan` і всі lint/test-джоби; кожна джоба з `needs` на умовні lint-джоби має `if` з `!cancelled()` та `!contains(needs.*.result, 'failure')` — skipped-лінт не блокує деплой, failed — блокує.
|
|
14
|
+
|
|
15
|
+
Що перевіряє rego: наявність plan-джоби з `ci plan --path … --github`; збіг `on.push.paths` ↔ `--path`; для кожної lint-джоби — `needs: plan`, гейт по outputs, той самий `--path`, `--no-fix`, prep-крок, `fetch-depth: 0`; для термінальних джоб — транзитивну досяжність plan і всіх перевірок та skip-толерантний `if`.
|
|
16
|
+
|
|
17
|
+
Що НЕ перевіряється (справа автора): добір доменів під вміст сервісу, зміст test/deploy-кроків, секрети/environment, uv-prep. Спільні workflow-інваріанти (persist-credentials, min-версії uses, існування paths-глобів) покривають `workflow_common` і `workflows`.
|
|
18
|
+
|
|
19
|
+
Repo-wide перевірки без path-підтримки (knip, jscpd, dep-policy) сюди НЕ входять — вони в окремому `lint-repo.yml` (концерн `lint_repo_yml`) і деплой не гейтять.
|
|
20
|
+
|
|
21
|
+
Канон-snippet (документаційний приклад): [deploy-service.yml.snippet.yml](./template/deploy-service.yml.snippet.yml)
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Форма сервіс-орієнтованого deploy-workflow (`.github/workflows/deploy-*.yml`).
|
|
2
|
+
#
|
|
3
|
+
# Канон (ga.mdc, сніпет template/deploy-service.yml.snippet.yml):
|
|
4
|
+
# plan (checkout fetch-depth:0 + prep + `bunx n-rules ci plan --path <svc> --github`)
|
|
5
|
+
# ├─ lint-<domain> × N (needs: plan; if: needs.plan.outputs.<domain> == 'true';
|
|
6
|
+
# │ `bunx n-rules lint <domain> --path <svc> --no-fix`)
|
|
7
|
+
# ├─ test (needs: plan)
|
|
8
|
+
# └─ deploy (needs-ланцюг досягає plan і ВСІ lint-джоби;
|
|
9
|
+
# if: !cancelled() + !contains(needs.*.result, 'failure'))
|
|
10
|
+
#
|
|
11
|
+
# Перелік сервісів консюмер-специфічний — концерн перевіряє ФОРМУ кожного
|
|
12
|
+
# знайденого deploy-*.yml, не реєстр. Ланцюги deploy (build → deploy) законні:
|
|
13
|
+
# досяжність рахується транзитивно через graph.reachable.
|
|
14
|
+
#
|
|
15
|
+
# Не перевіряється тут (лишається .mdc-каноном): вибір набору доменів під
|
|
16
|
+
# сервіс, зміст test/deploy-кроків, outputs-мапінг plan-джоби, uv-prep для
|
|
17
|
+
# python-стеку. Спільні workflow-перевірки (persist-credentials, min-версії
|
|
18
|
+
# uses) — у ga.workflow_common; існування paths-глобів — у ga/workflows.
|
|
19
|
+
package ga.service_deploy_workflow
|
|
20
|
+
|
|
21
|
+
import rego.v1
|
|
22
|
+
|
|
23
|
+
# ── extraction ──────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
jobs := object.get(input, "jobs", {})
|
|
26
|
+
|
|
27
|
+
# YAML-парсери розходяться щодо `on:`: YAML 1.2 → ключ "on", YAML 1.1 → bool true.
|
|
28
|
+
on_block := object.union(object.get(input, "on", {}), object.get(input, true, {}))
|
|
29
|
+
|
|
30
|
+
push_paths contains p if some p in object.get(object.get(on_block, "push", {}), "paths", [])
|
|
31
|
+
|
|
32
|
+
plan_runs contains r if {
|
|
33
|
+
some step in object.get(object.get(jobs, "plan", {}), "steps", [])
|
|
34
|
+
r := object.get(step, "run", "")
|
|
35
|
+
contains(r, "n-rules ci plan")
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# Сервісний каталог із команди plan-джоби (`--path <svc>`).
|
|
39
|
+
service_paths contains p if {
|
|
40
|
+
some r in plan_runs
|
|
41
|
+
m := regex.find_all_string_submatch_n(`n-rules ci plan\s+--path\s+(\S+)`, r, 1)
|
|
42
|
+
count(m) > 0
|
|
43
|
+
p := m[0][1]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Lint-джоби (крок `n-rules lint <domain> … --path …`) з розібраною командою.
|
|
47
|
+
lint_cmds contains cmd if {
|
|
48
|
+
some name, job in jobs
|
|
49
|
+
name != "plan"
|
|
50
|
+
some step in object.get(job, "steps", [])
|
|
51
|
+
r := object.get(step, "run", "")
|
|
52
|
+
m := regex.find_all_string_submatch_n(`n-rules lint\s+([a-z0-9-]+)\s+[^\n]*--path\s+(\S+)`, r, 1)
|
|
53
|
+
count(m) > 0
|
|
54
|
+
cmd := {"job": name, "domain": m[0][1], "path": m[0][2], "run": r}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
check_jobs contains cmd.job if some cmd in lint_cmds
|
|
58
|
+
|
|
59
|
+
# Граф залежностей і транзитивна досяжність (ланцюг deploy → build → lint).
|
|
60
|
+
needs_graph[name] := needs_of(job) if some name, job in jobs
|
|
61
|
+
|
|
62
|
+
all_needed contains n if {
|
|
63
|
+
some job in jobs
|
|
64
|
+
some n in needs_of(job)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# Термінальні джоби: ніхто від них не залежить (крім plan/lint-джоб).
|
|
68
|
+
terminal_jobs contains name if {
|
|
69
|
+
some name in object.keys(jobs)
|
|
70
|
+
name != "plan"
|
|
71
|
+
not name in check_jobs
|
|
72
|
+
not name in all_needed
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# ── deny: plan-джоба ────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
deny contains msg if {
|
|
78
|
+
count(jobs) > 0
|
|
79
|
+
not jobs.plan
|
|
80
|
+
msg := "deploy-workflow: немає job `plan` з кроком `bunx n-rules ci plan --path <svc> --github` (ga.mdc: сервіс-канон)"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
deny contains msg if {
|
|
84
|
+
jobs.plan
|
|
85
|
+
count(plan_runs) == 0
|
|
86
|
+
msg := "deploy-workflow: job `plan` без кроку `bunx n-rules ci plan …` (ga.mdc: сервіс-канон)"
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
deny contains msg if {
|
|
90
|
+
msg := "deploy-workflow: `ci plan` без `--path <serviceDir>` (ga.mdc: сервіс-канон)"
|
|
91
|
+
some r in plan_runs
|
|
92
|
+
not regex.match(`n-rules ci plan\s+--path\s+\S+`, r)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
deny contains msg if {
|
|
96
|
+
msg := "deploy-workflow: `ci plan` без `--github` — outputs не потраплять у $GITHUB_OUTPUT (ga.mdc)"
|
|
97
|
+
some r in plan_runs
|
|
98
|
+
not contains(r, "--github")
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# ── deny: тригер paths ↔ сервісний каталог ─────────────────────────────
|
|
102
|
+
|
|
103
|
+
deny contains msg if {
|
|
104
|
+
some sp in service_paths
|
|
105
|
+
not trigger_covers(sp)
|
|
106
|
+
msg := sprintf("deploy-workflow: on.push.paths не містить glob для сервісного каталогу %s (ga.mdc)", [sp])
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# ── deny: lint-джоби ────────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
deny contains msg if {
|
|
112
|
+
some cmd in lint_cmds
|
|
113
|
+
not "plan" in needs_of(jobs[cmd.job])
|
|
114
|
+
msg := sprintf("%s: lint-джоба мусить мати needs: plan (ga.mdc: сервіс-канон)", [cmd.job])
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
deny contains msg if {
|
|
118
|
+
some cmd in lint_cmds
|
|
119
|
+
cond := object.get(jobs[cmd.job], "if", "")
|
|
120
|
+
not contains(cond, sprintf("needs.plan.outputs.%s", [replace(cmd.domain, "-", "_")]))
|
|
121
|
+
msg := sprintf("%s: lint-джоба без гейта `if: needs.plan.outputs.%s == 'true'` — крок не скіпатиметься (ga.mdc)", [cmd.job, replace(cmd.domain, "-", "_")])
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
deny contains msg if {
|
|
125
|
+
count(service_paths) > 0
|
|
126
|
+
some cmd in lint_cmds
|
|
127
|
+
not cmd.path in service_paths
|
|
128
|
+
msg := sprintf("%s: `lint %s --path %s` ≠ `--path` plan-джоби — лінтиться інший каталог (ga.mdc)", [cmd.job, cmd.domain, cmd.path])
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
deny contains msg if {
|
|
132
|
+
some cmd in lint_cmds
|
|
133
|
+
not contains(cmd.run, "--no-fix")
|
|
134
|
+
msg := sprintf("%s: `n-rules lint` у CI мусить мати `--no-fix` (ga.mdc)", [cmd.job])
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
deny contains msg if {
|
|
138
|
+
some cmd in lint_cmds
|
|
139
|
+
not job_has_prep(jobs[cmd.job])
|
|
140
|
+
msg := sprintf("%s: lint-джоба без prep-кроку `uses: ./.github/actions/setup-bun-deps` (джоби не шарять ФС) (ga.mdc)", [cmd.job])
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# ── deny: fetch-depth для git-дельти ────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
deny contains msg if {
|
|
146
|
+
count(plan_runs) > 0
|
|
147
|
+
not job_has_full_checkout(jobs.plan)
|
|
148
|
+
msg := "plan: checkout мусить мати fetch-depth: 0 — без історії git-дельта `ci plan` не рахується (ga.mdc)"
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
deny contains msg if {
|
|
152
|
+
some cmd in lint_cmds
|
|
153
|
+
not job_has_full_checkout(jobs[cmd.job])
|
|
154
|
+
msg := sprintf("%s: checkout мусить мати fetch-depth: 0 — без історії `lint --path` не бачить дельти (ga.mdc)", [cmd.job])
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
# ── deny: термінальні (deploy) джоби ────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
deny contains msg if {
|
|
160
|
+
count(check_jobs) > 0
|
|
161
|
+
some name in terminal_jobs
|
|
162
|
+
not "plan" in graph.reachable(needs_graph, {name})
|
|
163
|
+
msg := sprintf("%s: термінальна джоба не досягає `plan` через needs-ланцюг (ga.mdc: сервіс-канон)", [name])
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
deny contains msg if {
|
|
167
|
+
some name in terminal_jobs
|
|
168
|
+
some cj in check_jobs
|
|
169
|
+
not cj in graph.reachable(needs_graph, {name})
|
|
170
|
+
msg := sprintf("%s: термінальна джоба не досягає перевірки `%s` через needs-ланцюг — деплой не гейтиться нею (ga.mdc)", [name, cj])
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
# Skipped-лінт (умовний гейт спрацював) не мусить блокувати деплой, а fail —
|
|
174
|
+
# мусить: канонічний if = `!cancelled() && !contains(needs.*.result, 'failure')`.
|
|
175
|
+
deny contains msg if {
|
|
176
|
+
some name in terminal_jobs
|
|
177
|
+
needed := needs_of(jobs[name])
|
|
178
|
+
count([cj | some cj in check_jobs; cj in needed]) > 0
|
|
179
|
+
cond := object.get(jobs[name], "if", "")
|
|
180
|
+
not skip_tolerant(cond)
|
|
181
|
+
msg := sprintf("%s: джоба з needs на умовні lint-джоби мусить мати if із `!cancelled()` і `!contains(needs.*.result, 'failure')` — інакше skipped-лінт назавжди блокує деплой (ga.mdc)", [name])
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
# ── helpers ─────────────────────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
# needs: string | array → array.
|
|
187
|
+
needs_of(job) := [n] if {
|
|
188
|
+
n := object.get(job, "needs", [])
|
|
189
|
+
is_string(n)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
needs_of(job) := object.get(job, "needs", []) if is_array(object.get(job, "needs", []))
|
|
193
|
+
|
|
194
|
+
trigger_covers(sp) if {
|
|
195
|
+
some p in push_paths
|
|
196
|
+
startswith(p, sp)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
job_has_prep(job) if {
|
|
200
|
+
some step in object.get(job, "steps", [])
|
|
201
|
+
step.uses == "./.github/actions/setup-bun-deps"
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
job_has_full_checkout(job) if {
|
|
205
|
+
some step in object.get(job, "steps", [])
|
|
206
|
+
startswith(object.get(step, "uses", ""), "actions/checkout@")
|
|
207
|
+
step.with["fetch-depth"] == 0
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
skip_tolerant(cond) if {
|
|
211
|
+
contains(cond, "!cancelled()")
|
|
212
|
+
contains(cond, "needs.*.result")
|
|
213
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Документаційний приклад сервіс-орієнтованого deploy-workflow (ga.mdc).
|
|
2
|
+
# Це НЕ deep-subset-канон (перелік сервісів консюмер-специфічний, назва файлу
|
|
3
|
+
# deploy-<service>.yml) — форму перевіряє service_deploy_workflow.rego.
|
|
4
|
+
# Плейсхолдер run/<service> заміни на каталог сервісу; набір lint-<domain>
|
|
5
|
+
# джоб добери під стеки сервісу (зайва джоба нешкідлива — ci plan її скіпне).
|
|
6
|
+
name: Deploy <service>
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
branches:
|
|
11
|
+
- dev
|
|
12
|
+
- main
|
|
13
|
+
paths:
|
|
14
|
+
- 'run/<service>/**'
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: ${{ github.ref }}-${{ github.workflow }}
|
|
18
|
+
cancel-in-progress: true
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
plan:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
permissions:
|
|
24
|
+
contents: read
|
|
25
|
+
outputs:
|
|
26
|
+
js: ${{ steps.plan.outputs.js }}
|
|
27
|
+
python: ${{ steps.plan.outputs.python }}
|
|
28
|
+
any: ${{ steps.plan.outputs.any }}
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v6
|
|
31
|
+
with:
|
|
32
|
+
persist-credentials: false
|
|
33
|
+
fetch-depth: 0
|
|
34
|
+
|
|
35
|
+
- uses: ./.github/actions/setup-bun-deps
|
|
36
|
+
|
|
37
|
+
- id: plan
|
|
38
|
+
run: bunx n-rules ci plan --path run/<service> --github
|
|
39
|
+
|
|
40
|
+
lint-js:
|
|
41
|
+
needs: plan
|
|
42
|
+
if: needs.plan.outputs.js == 'true'
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
permissions:
|
|
45
|
+
contents: read
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/checkout@v6
|
|
48
|
+
with:
|
|
49
|
+
persist-credentials: false
|
|
50
|
+
fetch-depth: 0
|
|
51
|
+
|
|
52
|
+
- uses: ./.github/actions/setup-bun-deps
|
|
53
|
+
|
|
54
|
+
- run: bunx n-rules lint js --path run/<service> --no-fix
|
|
55
|
+
|
|
56
|
+
# …lint-<domain> для кожного домену сервісу (python/docker/k8s/security/…)
|
|
57
|
+
|
|
58
|
+
test:
|
|
59
|
+
needs: plan
|
|
60
|
+
if: needs.plan.outputs.any == 'true'
|
|
61
|
+
runs-on: ubuntu-latest
|
|
62
|
+
permissions:
|
|
63
|
+
contents: read
|
|
64
|
+
steps:
|
|
65
|
+
- uses: actions/checkout@v6
|
|
66
|
+
with:
|
|
67
|
+
persist-credentials: false
|
|
68
|
+
fetch-depth: 0
|
|
69
|
+
|
|
70
|
+
- uses: ./.github/actions/setup-bun-deps
|
|
71
|
+
|
|
72
|
+
# python-стек: перед тестами ще uv (uses: astral-sh/setup-uv@v7 + uv sync --locked)
|
|
73
|
+
- run: bun test run/<service>
|
|
74
|
+
|
|
75
|
+
deploy:
|
|
76
|
+
needs:
|
|
77
|
+
- plan
|
|
78
|
+
- lint-js
|
|
79
|
+
- test
|
|
80
|
+
# Skipped-лінт (гейт ci plan) НЕ блокує деплой; failed — блокує.
|
|
81
|
+
if: ${{ !cancelled() && needs.plan.result == 'success' && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }}
|
|
82
|
+
runs-on: ubuntu-latest
|
|
83
|
+
steps:
|
|
84
|
+
- uses: actions/checkout@v6
|
|
85
|
+
with:
|
|
86
|
+
persist-credentials: false
|
|
87
|
+
|
|
88
|
+
# …кроки деплою сервісу (build/push/apply)
|