opsscript-gate 0.6.3__tar.gz → 0.7.0__tar.gz
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.
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/.github/workflow-templates/opsscript-gate.yml +1 -1
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/CHANGELOG.md +6 -1
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/CITATION.cff +1 -1
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/PKG-INFO +37 -19
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/README.md +36 -18
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/README.zh-CN.md +8 -4
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/action.yml +1 -1
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/docs/configuration.md +3 -3
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/docs/gpt-oss.md +1 -1
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/examples/github-actions/README.md +1 -1
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/examples/github-actions/workflow.yml +1 -1
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/pyproject.toml +1 -1
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/src/opsscript_gate/__init__.py +1 -1
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/src/opsscript_gate/cli.py +8 -2
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/src/opsscript_gate/config.py +1 -1
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/src/opsscript_gate/reporter.py +120 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/tests/test_action_contract.py +1 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/tests/test_runner.py +50 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/.gitattributes +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/.github/ISSUE_TEMPLATE/distro_request.yml +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/.github/assets/social-preview.png +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/.github/pull_request_template.md +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/.github/workflow-templates/properties.json +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/.github/workflows/demo.yml +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/.github/workflows/release.yml +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/.github/workflows/test.yml +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/.gitignore +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/CONTRIBUTING.md +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/LICENSE +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/ROADMAP.md +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/SECURITY.md +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/docs/troubleshooting.md +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/examples/alpine-incompatibility/README.md +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/examples/alpine-incompatibility/apt_dependency.sh +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/examples/basic/README.md +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/examples/basic/clean_setup.sh +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/examples/interactive-hang/README.md +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/examples/interactive-hang/prompt_confirm.sh +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/src/opsscript_gate/discovery.py +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/src/opsscript_gate/models.py +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/src/opsscript_gate/remediation.py +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/src/opsscript_gate/runner.py +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/tests/fixtures/fail_deps.sh +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/tests/fixtures/fail_hang.sh +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/tests/fixtures/pass_basic.sh +0 -0
- {opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/tests/test_onboarding.py +0 -0
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Added SARIF 2.1.0 output for location-aware GitHub Code Scanning integration.
|
|
7
|
+
|
|
8
|
+
## [0.7.0] - 2026-10-01
|
|
4
9
|
|
|
5
10
|
### Added
|
|
6
11
|
- Added `opsscript-gate doctor` to diagnose Python and Docker prerequisites before a local run.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: opsscript-gate
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Run Linux shell scripts in Debian, Ubuntu and Alpine containers to catch runtime compatibility failures before merge
|
|
5
5
|
Project-URL: Homepage, https://mresyzz.github.io/InstallReady/
|
|
6
6
|
Project-URL: Demo, https://mresyzz.github.io/InstallReady/?repo=Mresyzz%2Fopsscript-gate
|
|
@@ -48,15 +48,15 @@ Description-Content-Type: text/markdown
|
|
|
48
48
|
It is designed for shell testing, portable shell validation, Bash/POSIX compatibility checks, and cross-distro CI where syntax-only tooling is not enough.
|
|
49
49
|
|
|
50
50
|
If your repository contains an installer, bootstrap script, or shell entrypoint, the fastest path is to
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
open the [InstallReady scanner](https://mresyzz.github.io/InstallReady/), enter `owner/repository`, and
|
|
52
|
+
choose **Add workflow on GitHub**. It generates a reviewable workflow without an account, API key, or
|
|
53
|
+
service signup. You can also copy the maintained [zero-configuration workflow](.github/workflow-templates/opsscript-gate.yml).
|
|
54
54
|
|
|
55
55
|
Try the browser scanner: [InstallReady](https://mresyzz.github.io/InstallReady/?repo=Mresyzz%2Fopsscript-gate) checks this repository without executing code or requiring a login.
|
|
56
56
|
|
|
57
57
|
---
|
|
58
58
|
|
|
59
|
-
## Current release: v0.
|
|
59
|
+
## Current release: v0.7.0
|
|
60
60
|
|
|
61
61
|
The current release includes a project config file, a dry-run plan, presets, exclusions,
|
|
62
62
|
saved reports, and changed-script selection for pull requests. The project controls
|
|
@@ -86,8 +86,9 @@ See [configuration and migration](docs/configuration.md) and
|
|
|
86
86
|
|
|
87
87
|
Add this workflow to `.github/workflows/shell-compat.yml`:
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
The repository includes a maintained [workflow template](.github/workflow-templates/opsscript-gate.yml).
|
|
90
|
+
For a one-click setup in another public repository, use the InstallReady scanner above; it opens GitHub's
|
|
91
|
+
workflow editor with the generated file ready to review.
|
|
91
92
|
|
|
92
93
|
You can copy the maintained example from [`examples/github-actions/workflow.yml`](examples/github-actions/workflow.yml)
|
|
93
94
|
and change the `script-path`, or use the minimal workflow below.
|
|
@@ -101,7 +102,9 @@ jobs:
|
|
|
101
102
|
runs-on: ubuntu-latest
|
|
102
103
|
steps:
|
|
103
104
|
- uses: actions/checkout@v7
|
|
104
|
-
|
|
105
|
+
with:
|
|
106
|
+
persist-credentials: false
|
|
107
|
+
- uses: Mresyzz/opsscript-gate@v0.7.0
|
|
105
108
|
```
|
|
106
109
|
|
|
107
110
|
This zero-configuration form discovers shell scripts in the repository. Use
|
|
@@ -112,7 +115,7 @@ repository has a single installer. For self-hosted model installers, see the
|
|
|
112
115
|
Or test a specific script with custom execution modes:
|
|
113
116
|
|
|
114
117
|
```yaml
|
|
115
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
118
|
+
- uses: Mresyzz/opsscript-gate@v0.7.0
|
|
116
119
|
with:
|
|
117
120
|
script-path: scripts/install.sh
|
|
118
121
|
shell: auto
|
|
@@ -142,8 +145,9 @@ unrelated scripts:
|
|
|
142
145
|
```yaml
|
|
143
146
|
- uses: actions/checkout@v7
|
|
144
147
|
with:
|
|
148
|
+
persist-credentials: false
|
|
145
149
|
fetch-depth: 0
|
|
146
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
150
|
+
- uses: Mresyzz/opsscript-gate@v0.7.0
|
|
147
151
|
with:
|
|
148
152
|
changed-since: ${{ github.event.pull_request.base.sha }}
|
|
149
153
|
preset: minimal
|
|
@@ -220,6 +224,28 @@ The action also writes a markdown matrix to `$GITHUB_STEP_SUMMARY`:
|
|
|
220
224
|
</details>
|
|
221
225
|
```
|
|
222
226
|
|
|
227
|
+
### SARIF for Code Scanning
|
|
228
|
+
|
|
229
|
+
Use `sarif` when the result should appear in GitHub Code Scanning or another
|
|
230
|
+
SARIF-compatible viewer. Upload it explicitly with the official upload action:
|
|
231
|
+
|
|
232
|
+
```yaml
|
|
233
|
+
- uses: Mresyzz/opsscript-gate@v0.7.0
|
|
234
|
+
with:
|
|
235
|
+
script-path: scripts/install.sh
|
|
236
|
+
format: sarif
|
|
237
|
+
output: reports/opsscript-gate.sarif
|
|
238
|
+
|
|
239
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
240
|
+
if: always()
|
|
241
|
+
with:
|
|
242
|
+
sarif_file: reports/opsscript-gate.sarif
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Each runtime failure becomes a location-aware result with the distribution,
|
|
246
|
+
exit code, command, and diagnostic line. A passing matrix produces a valid SARIF
|
|
247
|
+
file with no findings.
|
|
248
|
+
|
|
223
249
|
---
|
|
224
250
|
|
|
225
251
|
## Example: runtime behavior that static analysis misses
|
|
@@ -376,7 +402,7 @@ usage: opsscript-gate run [-h] [--matrix MATRIX] [-j JOBS] [--timeout TIMEOUT]
|
|
|
376
402
|
| `-j, --jobs` | Integer | `min(2, size)` | Number of concurrent container jobs |
|
|
377
403
|
| `--matrix` | String | `debian:12-slim,ubuntu:22.04,ubuntu:24.04,alpine:3.20` | Comma-separated list of Docker images |
|
|
378
404
|
| `--timeout` | Integer | `60` | Hard timeout per container in seconds |
|
|
379
|
-
| `--format` | Choice | `table` | Output format: `table`, `markdown`, or `
|
|
405
|
+
| `--format` | Choice | `table` | Output format: `table`, `markdown`, `json`, or `sarif` |
|
|
380
406
|
| `--shell` | Choice | `posix` | Execution mode: `posix` (default), `shebang`, or `auto` |
|
|
381
407
|
| `--mem-limit` | String | `256m` | Memory limit per container (e.g. `256m`, `512m`) |
|
|
382
408
|
| `--pids-limit`| Integer | `128` | Maximum number of processes per container |
|
|
@@ -413,14 +439,6 @@ usage: opsscript-gate run [-h] [--matrix MATRIX] [-j JOBS] [--timeout TIMEOUT]
|
|
|
413
439
|
Explicit CLI options override project settings. Explicit script paths bypass discovery
|
|
414
440
|
and its exclusions. See [complete configuration semantics](docs/configuration.md).
|
|
415
441
|
|
|
416
|
-
## Downstream example
|
|
417
|
-
|
|
418
|
-
[`Mresyzz/linux-dev-bootstrap`](https://github.com/Mresyzz/linux-dev-bootstrap) contains a workflow that validates `install.sh` across the default Debian, Ubuntu, and Alpine matrix.
|
|
419
|
-
|
|
420
|
-
See the downstream workflow: [`.github/workflows/test.yml`](https://github.com/Mresyzz/linux-dev-bootstrap/blob/main/.github/workflows/test.yml).
|
|
421
|
-
|
|
422
|
-
---
|
|
423
|
-
|
|
424
442
|
## 🧪 Development & Testing
|
|
425
443
|
|
|
426
444
|
```bash
|
|
@@ -18,15 +18,15 @@
|
|
|
18
18
|
It is designed for shell testing, portable shell validation, Bash/POSIX compatibility checks, and cross-distro CI where syntax-only tooling is not enough.
|
|
19
19
|
|
|
20
20
|
If your repository contains an installer, bootstrap script, or shell entrypoint, the fastest path is to
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
open the [InstallReady scanner](https://mresyzz.github.io/InstallReady/), enter `owner/repository`, and
|
|
22
|
+
choose **Add workflow on GitHub**. It generates a reviewable workflow without an account, API key, or
|
|
23
|
+
service signup. You can also copy the maintained [zero-configuration workflow](.github/workflow-templates/opsscript-gate.yml).
|
|
24
24
|
|
|
25
25
|
Try the browser scanner: [InstallReady](https://mresyzz.github.io/InstallReady/?repo=Mresyzz%2Fopsscript-gate) checks this repository without executing code or requiring a login.
|
|
26
26
|
|
|
27
27
|
---
|
|
28
28
|
|
|
29
|
-
## Current release: v0.
|
|
29
|
+
## Current release: v0.7.0
|
|
30
30
|
|
|
31
31
|
The current release includes a project config file, a dry-run plan, presets, exclusions,
|
|
32
32
|
saved reports, and changed-script selection for pull requests. The project controls
|
|
@@ -56,8 +56,9 @@ See [configuration and migration](docs/configuration.md) and
|
|
|
56
56
|
|
|
57
57
|
Add this workflow to `.github/workflows/shell-compat.yml`:
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
The repository includes a maintained [workflow template](.github/workflow-templates/opsscript-gate.yml).
|
|
60
|
+
For a one-click setup in another public repository, use the InstallReady scanner above; it opens GitHub's
|
|
61
|
+
workflow editor with the generated file ready to review.
|
|
61
62
|
|
|
62
63
|
You can copy the maintained example from [`examples/github-actions/workflow.yml`](examples/github-actions/workflow.yml)
|
|
63
64
|
and change the `script-path`, or use the minimal workflow below.
|
|
@@ -71,7 +72,9 @@ jobs:
|
|
|
71
72
|
runs-on: ubuntu-latest
|
|
72
73
|
steps:
|
|
73
74
|
- uses: actions/checkout@v7
|
|
74
|
-
|
|
75
|
+
with:
|
|
76
|
+
persist-credentials: false
|
|
77
|
+
- uses: Mresyzz/opsscript-gate@v0.7.0
|
|
75
78
|
```
|
|
76
79
|
|
|
77
80
|
This zero-configuration form discovers shell scripts in the repository. Use
|
|
@@ -82,7 +85,7 @@ repository has a single installer. For self-hosted model installers, see the
|
|
|
82
85
|
Or test a specific script with custom execution modes:
|
|
83
86
|
|
|
84
87
|
```yaml
|
|
85
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
88
|
+
- uses: Mresyzz/opsscript-gate@v0.7.0
|
|
86
89
|
with:
|
|
87
90
|
script-path: scripts/install.sh
|
|
88
91
|
shell: auto
|
|
@@ -112,8 +115,9 @@ unrelated scripts:
|
|
|
112
115
|
```yaml
|
|
113
116
|
- uses: actions/checkout@v7
|
|
114
117
|
with:
|
|
118
|
+
persist-credentials: false
|
|
115
119
|
fetch-depth: 0
|
|
116
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
120
|
+
- uses: Mresyzz/opsscript-gate@v0.7.0
|
|
117
121
|
with:
|
|
118
122
|
changed-since: ${{ github.event.pull_request.base.sha }}
|
|
119
123
|
preset: minimal
|
|
@@ -190,6 +194,28 @@ The action also writes a markdown matrix to `$GITHUB_STEP_SUMMARY`:
|
|
|
190
194
|
</details>
|
|
191
195
|
```
|
|
192
196
|
|
|
197
|
+
### SARIF for Code Scanning
|
|
198
|
+
|
|
199
|
+
Use `sarif` when the result should appear in GitHub Code Scanning or another
|
|
200
|
+
SARIF-compatible viewer. Upload it explicitly with the official upload action:
|
|
201
|
+
|
|
202
|
+
```yaml
|
|
203
|
+
- uses: Mresyzz/opsscript-gate@v0.7.0
|
|
204
|
+
with:
|
|
205
|
+
script-path: scripts/install.sh
|
|
206
|
+
format: sarif
|
|
207
|
+
output: reports/opsscript-gate.sarif
|
|
208
|
+
|
|
209
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
210
|
+
if: always()
|
|
211
|
+
with:
|
|
212
|
+
sarif_file: reports/opsscript-gate.sarif
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Each runtime failure becomes a location-aware result with the distribution,
|
|
216
|
+
exit code, command, and diagnostic line. A passing matrix produces a valid SARIF
|
|
217
|
+
file with no findings.
|
|
218
|
+
|
|
193
219
|
---
|
|
194
220
|
|
|
195
221
|
## Example: runtime behavior that static analysis misses
|
|
@@ -346,7 +372,7 @@ usage: opsscript-gate run [-h] [--matrix MATRIX] [-j JOBS] [--timeout TIMEOUT]
|
|
|
346
372
|
| `-j, --jobs` | Integer | `min(2, size)` | Number of concurrent container jobs |
|
|
347
373
|
| `--matrix` | String | `debian:12-slim,ubuntu:22.04,ubuntu:24.04,alpine:3.20` | Comma-separated list of Docker images |
|
|
348
374
|
| `--timeout` | Integer | `60` | Hard timeout per container in seconds |
|
|
349
|
-
| `--format` | Choice | `table` | Output format: `table`, `markdown`, or `
|
|
375
|
+
| `--format` | Choice | `table` | Output format: `table`, `markdown`, `json`, or `sarif` |
|
|
350
376
|
| `--shell` | Choice | `posix` | Execution mode: `posix` (default), `shebang`, or `auto` |
|
|
351
377
|
| `--mem-limit` | String | `256m` | Memory limit per container (e.g. `256m`, `512m`) |
|
|
352
378
|
| `--pids-limit`| Integer | `128` | Maximum number of processes per container |
|
|
@@ -383,14 +409,6 @@ usage: opsscript-gate run [-h] [--matrix MATRIX] [-j JOBS] [--timeout TIMEOUT]
|
|
|
383
409
|
Explicit CLI options override project settings. Explicit script paths bypass discovery
|
|
384
410
|
and its exclusions. See [complete configuration semantics](docs/configuration.md).
|
|
385
411
|
|
|
386
|
-
## Downstream example
|
|
387
|
-
|
|
388
|
-
[`Mresyzz/linux-dev-bootstrap`](https://github.com/Mresyzz/linux-dev-bootstrap) contains a workflow that validates `install.sh` across the default Debian, Ubuntu, and Alpine matrix.
|
|
389
|
-
|
|
390
|
-
See the downstream workflow: [`.github/workflows/test.yml`](https://github.com/Mresyzz/linux-dev-bootstrap/blob/main/.github/workflows/test.yml).
|
|
391
|
-
|
|
392
|
-
---
|
|
393
|
-
|
|
394
412
|
## 🧪 Development & Testing
|
|
395
413
|
|
|
396
414
|
```bash
|
|
@@ -10,9 +10,9 @@ Bash 依赖、包管理器假设和交互式阻塞。它与 ShellCheck 互补。
|
|
|
10
10
|
|
|
11
11
|
也可以直接打开 [InstallReady 在线扫描器](https://mresyzz.github.io/InstallReady/?repo=Mresyzz%2Fopsscript-gate),无需登录即可检查公开仓库。
|
|
12
12
|
|
|
13
|
-
## 当前版本 v0.
|
|
13
|
+
## 当前版本 v0.7.0
|
|
14
14
|
|
|
15
|
-
v0.
|
|
15
|
+
v0.7.0 已作为正式版本发布;如果你要从源码验证当前分支,也可以执行:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
pip install -e .
|
|
@@ -28,11 +28,15 @@ opsscript-gate run --dry-run
|
|
|
28
28
|
opsscript-gate run --format json --output reports/compatibility.json
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
`init` 生成配置和 GitHub 工作流,不覆盖已有文件。工作流引用 `v0.
|
|
31
|
+
`init` 生成配置和 GitHub 工作流,不覆盖已有文件。工作流引用 `v0.7.0`。
|
|
32
32
|
预览不需要 Docker;真实运行需要 Python 3.10+
|
|
33
33
|
和可访问的 Linux Docker 引擎。
|
|
34
34
|
`opsscript-gate doctor` 可以在真实运行前检查 Python 和 Docker。
|
|
35
35
|
|
|
36
|
+
需要接入 GitHub Code Scanning 时,将 `--format sarif --output reports/opsscript-gate.sarif`
|
|
37
|
+
传给运行命令,再用 `github/codeql-action/upload-sarif@v3` 上传报告。每个失败发行版
|
|
38
|
+
都会保留脚本路径、诊断行号、命令和退出码;全部通过时仍会生成合法的空 SARIF 报告。
|
|
39
|
+
|
|
36
40
|
生成的配置默认排除 `tests/*`、`examples/*`,使用 Debian + Alpine,尊重
|
|
37
41
|
脚本 shebang,并关闭网络。请根据实际脚本审核配置;需要下载文件时启用
|
|
38
42
|
`--network bridge`。保留现有无配置行为:四个发行版、POSIX shell、bridge 网络。
|
|
@@ -58,7 +62,7 @@ Docker daemon 和可选的项目配置。
|
|
|
58
62
|
- uses: actions/checkout@v7
|
|
59
63
|
with:
|
|
60
64
|
fetch-depth: 0
|
|
61
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
65
|
+
- uses: Mresyzz/opsscript-gate@v0.7.0
|
|
62
66
|
with:
|
|
63
67
|
changed-since: ${{ github.event.pull_request.base.sha }}
|
|
64
68
|
preset: minimal
|
|
@@ -12,7 +12,7 @@ opsscript-gate run --dry-run
|
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
`init` refuses to overwrite either `.opsscript-gate.json` or
|
|
15
|
-
`.github/workflows/opsscript-gate.yml`. The generated workflow uses the v0.
|
|
15
|
+
`.github/workflows/opsscript-gate.yml`. The generated workflow uses the v0.7.0 tag.
|
|
16
16
|
|
|
17
17
|
`doctor` checks Python and Docker connectivity without executing repository scripts.
|
|
18
18
|
Use `opsscript-gate doctor --format json` when collecting a support report.
|
|
@@ -99,7 +99,7 @@ For pull requests, set `changed-since` to the base commit and fetch full Git his
|
|
|
99
99
|
- uses: actions/checkout@v7
|
|
100
100
|
with:
|
|
101
101
|
fetch-depth: 0
|
|
102
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
102
|
+
- uses: Mresyzz/opsscript-gate@v0.7.0
|
|
103
103
|
with:
|
|
104
104
|
changed-since: ${{ github.event.pull_request.base.sha }}
|
|
105
105
|
preset: minimal
|
|
@@ -109,7 +109,7 @@ The Action passes when no changed shell scripts are selected. This keeps unrelat
|
|
|
109
109
|
documentation or application changes from starting container jobs.
|
|
110
110
|
|
|
111
111
|
```yaml
|
|
112
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
112
|
+
- uses: Mresyzz/opsscript-gate@v0.7.0
|
|
113
113
|
with:
|
|
114
114
|
config: .opsscript-gate.json
|
|
115
115
|
format: json
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
|
|
3
3
|
This example shows a GitHub Actions workflow for checking a shell script on pull requests.
|
|
4
4
|
|
|
5
|
-
Copy `workflow.yml` into your repository at `.github/workflows/script-gate.yml` and point `script-path` to the script you want to validate. The example uses the current v0.
|
|
5
|
+
Copy `workflow.yml` into your repository at `.github/workflows/script-gate.yml` and point `script-path` to the script you want to validate. The example uses the current v0.7.0 Action release.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "opsscript-gate"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.7.0"
|
|
8
8
|
description = "Run Linux shell scripts in Debian, Ubuntu and Alpine containers to catch runtime compatibility failures before merge"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -15,8 +15,10 @@ from opsscript_gate.reporter import (
|
|
|
15
15
|
emit_github_annotations,
|
|
16
16
|
format_github_summary,
|
|
17
17
|
format_json,
|
|
18
|
+
format_multi_sarif,
|
|
18
19
|
format_multi_github_summary,
|
|
19
20
|
format_multi_terminal_table,
|
|
21
|
+
format_sarif,
|
|
20
22
|
format_terminal_table,
|
|
21
23
|
write_github_step_summary,
|
|
22
24
|
)
|
|
@@ -91,9 +93,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
91
93
|
)
|
|
92
94
|
run_parser.add_argument(
|
|
93
95
|
"--format",
|
|
94
|
-
choices=["table", "markdown", "json"],
|
|
96
|
+
choices=["table", "markdown", "json", "sarif"],
|
|
95
97
|
default="table",
|
|
96
|
-
help="Output report format: 'table' (default ASCII), 'markdown', or '
|
|
98
|
+
help="Output report format: 'table' (default ASCII), 'markdown', 'json', or 'sarif'",
|
|
97
99
|
)
|
|
98
100
|
run_parser.add_argument(
|
|
99
101
|
"--shell",
|
|
@@ -301,6 +303,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
301
303
|
# Format report output
|
|
302
304
|
if args.format == "json":
|
|
303
305
|
output = format_json(report)
|
|
306
|
+
elif args.format == "sarif":
|
|
307
|
+
output = format_sarif(report, script_path=script_path)
|
|
304
308
|
elif args.format == "markdown":
|
|
305
309
|
output = format_github_summary(report, script_path=script_path)
|
|
306
310
|
else:
|
|
@@ -353,6 +357,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
353
357
|
|
|
354
358
|
if args.format == "json":
|
|
355
359
|
output = format_json(multi_report)
|
|
360
|
+
elif args.format == "sarif":
|
|
361
|
+
output = format_multi_sarif(multi_report)
|
|
356
362
|
elif args.format == "markdown":
|
|
357
363
|
output = format_multi_github_summary(multi_report)
|
|
358
364
|
else:
|
|
@@ -3,6 +3,7 @@ import json
|
|
|
3
3
|
import os
|
|
4
4
|
import re
|
|
5
5
|
import sys
|
|
6
|
+
from opsscript_gate import __version__
|
|
6
7
|
from opsscript_gate.models import DistroStatus, MultiScriptReport, RunReport, SingleResult
|
|
7
8
|
|
|
8
9
|
# Structure-altering Markdown characters in regular prose:
|
|
@@ -463,6 +464,125 @@ def format_json(report: RunReport | MultiScriptReport) -> str:
|
|
|
463
464
|
return json.dumps(report.to_dict(), indent=2, ensure_ascii=False)
|
|
464
465
|
|
|
465
466
|
|
|
467
|
+
_SARIF_SCHEMA = "https://json.schemastore.org/sarif-2.1.0.json"
|
|
468
|
+
_SARIF_RULES = {
|
|
469
|
+
DistroStatus.FAIL: ("OSG001", "Runtime compatibility failure"),
|
|
470
|
+
DistroStatus.TIMED_OUT: ("OSG002", "Runtime compatibility check timed out"),
|
|
471
|
+
DistroStatus.ERROR: ("OSG003", "Runtime compatibility check error"),
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def _sarif_path(script_path: str) -> str:
|
|
476
|
+
"""Return a repository-relative SARIF artifact path."""
|
|
477
|
+
clean = (script_path or "script.sh").replace("\\", "/")
|
|
478
|
+
while clean.startswith("./"):
|
|
479
|
+
clean = clean[2:]
|
|
480
|
+
return clean or "script.sh"
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def _sarif_message(result: SingleResult) -> str:
|
|
484
|
+
parts = [f"[{result.distro}] {result.status.value}"]
|
|
485
|
+
if result.diagnostic:
|
|
486
|
+
parts.append(result.diagnostic.message)
|
|
487
|
+
if result.diagnostic.hint:
|
|
488
|
+
parts.append(result.diagnostic.hint)
|
|
489
|
+
elif result.error_message:
|
|
490
|
+
parts.append(result.error_message)
|
|
491
|
+
elif result.exit_code is not None:
|
|
492
|
+
parts.append(f"Execution exited with code {result.exit_code}.")
|
|
493
|
+
return " — ".join(parts)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def _sarif_result(result: SingleResult, script_path: str) -> dict[str, object] | None:
|
|
497
|
+
if result.status == DistroStatus.PASS:
|
|
498
|
+
return None
|
|
499
|
+
|
|
500
|
+
rule_id, rule_name = _SARIF_RULES[result.status]
|
|
501
|
+
physical_location: dict[str, object] = {
|
|
502
|
+
"artifactLocation": {
|
|
503
|
+
"uri": _sarif_path(script_path),
|
|
504
|
+
},
|
|
505
|
+
}
|
|
506
|
+
if result.diagnostic and result.diagnostic.line and result.diagnostic.line > 0:
|
|
507
|
+
physical_location["region"] = {"startLine": result.diagnostic.line}
|
|
508
|
+
|
|
509
|
+
properties: dict[str, object] = {
|
|
510
|
+
"distribution": result.distro,
|
|
511
|
+
"status": result.status.value,
|
|
512
|
+
"durationSeconds": round(result.duration, 3),
|
|
513
|
+
}
|
|
514
|
+
if result.exit_code is not None:
|
|
515
|
+
properties["exitCode"] = result.exit_code
|
|
516
|
+
if result.diagnostic and result.diagnostic.command:
|
|
517
|
+
properties["command"] = result.diagnostic.command
|
|
518
|
+
|
|
519
|
+
return {
|
|
520
|
+
"ruleId": rule_id,
|
|
521
|
+
"level": "error",
|
|
522
|
+
"message": {"text": _sarif_message(result)},
|
|
523
|
+
"locations": [{"physicalLocation": physical_location}],
|
|
524
|
+
"properties": properties,
|
|
525
|
+
"help": {
|
|
526
|
+
"text": rule_name,
|
|
527
|
+
"uri": "https://github.com/Mresyzz/opsscript-gate#ci-output",
|
|
528
|
+
},
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def _sarif_run(results: list[dict[str, object]]) -> dict[str, object]:
|
|
533
|
+
return {
|
|
534
|
+
"tool": {
|
|
535
|
+
"driver": {
|
|
536
|
+
"name": "OpsScript Gate",
|
|
537
|
+
"version": __version__,
|
|
538
|
+
"informationUri": "https://github.com/Mresyzz/opsscript-gate",
|
|
539
|
+
"rules": [
|
|
540
|
+
{
|
|
541
|
+
"id": rule_id,
|
|
542
|
+
"name": rule_name,
|
|
543
|
+
"shortDescription": {"text": rule_name},
|
|
544
|
+
"helpUri": "https://github.com/Mresyzz/opsscript-gate#ci-output",
|
|
545
|
+
}
|
|
546
|
+
for rule_id, rule_name in _SARIF_RULES.values()
|
|
547
|
+
],
|
|
548
|
+
},
|
|
549
|
+
},
|
|
550
|
+
"results": results,
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def format_sarif(report: RunReport, script_path: str = "") -> str:
|
|
555
|
+
"""Format runtime failures as SARIF 2.1.0 for GitHub Code Scanning."""
|
|
556
|
+
results = [
|
|
557
|
+
item
|
|
558
|
+
for result in report.results
|
|
559
|
+
if (item := _sarif_result(result, script_path)) is not None
|
|
560
|
+
]
|
|
561
|
+
payload = {
|
|
562
|
+
"$schema": _SARIF_SCHEMA,
|
|
563
|
+
"version": "2.1.0",
|
|
564
|
+
"runs": [_sarif_run(results)],
|
|
565
|
+
}
|
|
566
|
+
return json.dumps(payload, indent=2, ensure_ascii=False)
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def format_multi_sarif(report: MultiScriptReport) -> str:
|
|
570
|
+
"""Format multi-script runtime failures as one SARIF 2.1.0 run."""
|
|
571
|
+
results: list[dict[str, object]] = []
|
|
572
|
+
for script_path, script_report in report.reports.items():
|
|
573
|
+
results.extend(
|
|
574
|
+
item
|
|
575
|
+
for result in script_report.results
|
|
576
|
+
if (item := _sarif_result(result, script_path)) is not None
|
|
577
|
+
)
|
|
578
|
+
payload = {
|
|
579
|
+
"$schema": _SARIF_SCHEMA,
|
|
580
|
+
"version": "2.1.0",
|
|
581
|
+
"runs": [_sarif_run(results)],
|
|
582
|
+
}
|
|
583
|
+
return json.dumps(payload, indent=2, ensure_ascii=False)
|
|
584
|
+
|
|
585
|
+
|
|
466
586
|
def write_github_step_summary(
|
|
467
587
|
report_or_content: RunReport | MultiScriptReport | str,
|
|
468
588
|
script_path: str = "",
|
|
@@ -14,6 +14,7 @@ def test_action_metadata_is_valid_and_inputs_are_wired():
|
|
|
14
14
|
"--network", "--matrix", "--jobs", "--config", "--preset",
|
|
15
15
|
"--max-scripts", "--output", "--exclude"):
|
|
16
16
|
assert flag in run_text
|
|
17
|
+
assert "json, or sarif" in text
|
|
17
18
|
assert "--changed-since" in run_text
|
|
18
19
|
|
|
19
20
|
|
|
@@ -19,8 +19,10 @@ from opsscript_gate.reporter import (
|
|
|
19
19
|
format_github_annotations,
|
|
20
20
|
format_github_summary,
|
|
21
21
|
format_json,
|
|
22
|
+
format_multi_sarif,
|
|
22
23
|
format_multi_github_summary,
|
|
23
24
|
format_multi_terminal_table,
|
|
25
|
+
format_sarif,
|
|
24
26
|
format_terminal_table,
|
|
25
27
|
write_github_step_summary,
|
|
26
28
|
)
|
|
@@ -300,6 +302,54 @@ def test_reporter_json():
|
|
|
300
302
|
assert data["results"][0]["status"] == "PASS"
|
|
301
303
|
|
|
302
304
|
|
|
305
|
+
def test_reporter_sarif_contains_failure_location_and_metadata():
|
|
306
|
+
result = SingleResult(
|
|
307
|
+
"alpine:3.20",
|
|
308
|
+
DistroStatus.FAIL,
|
|
309
|
+
127,
|
|
310
|
+
0.42,
|
|
311
|
+
diagnostic=FailureDiagnostic(
|
|
312
|
+
kind="missing_command",
|
|
313
|
+
message="apt-get: command not found",
|
|
314
|
+
command="apt-get",
|
|
315
|
+
line=4,
|
|
316
|
+
distro="alpine:3.20",
|
|
317
|
+
hint="Use apk instead of apt-get.",
|
|
318
|
+
),
|
|
319
|
+
)
|
|
320
|
+
report = RunReport([SingleResult("debian:12-slim", DistroStatus.PASS, 0, 0.2), result])
|
|
321
|
+
|
|
322
|
+
payload = json.loads(format_sarif(report, script_path="./scripts/install.sh"))
|
|
323
|
+
assert payload["$schema"].endswith("sarif-2.1.0.json")
|
|
324
|
+
assert payload["version"] == "2.1.0"
|
|
325
|
+
finding = payload["runs"][0]["results"][0]
|
|
326
|
+
assert finding["ruleId"] == "OSG001"
|
|
327
|
+
assert finding["level"] == "error"
|
|
328
|
+
assert finding["locations"][0]["physicalLocation"]["artifactLocation"]["uri"] == "scripts/install.sh"
|
|
329
|
+
assert finding["locations"][0]["physicalLocation"]["region"]["startLine"] == 4
|
|
330
|
+
assert finding["properties"]["distribution"] == "alpine:3.20"
|
|
331
|
+
assert finding["properties"]["command"] == "apt-get"
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def test_reporter_multi_sarif_keeps_script_paths():
|
|
335
|
+
report = MultiScriptReport(
|
|
336
|
+
reports={
|
|
337
|
+
"install.sh": RunReport([SingleResult("alpine:3.20", DistroStatus.FAIL, 1, 0.1)]),
|
|
338
|
+
"scripts/setup.sh": RunReport([SingleResult("debian:12-slim", DistroStatus.TIMED_OUT, None, 5.0)]),
|
|
339
|
+
},
|
|
340
|
+
total_duration=5.1,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
payload = json.loads(format_multi_sarif(report))
|
|
344
|
+
results = payload["runs"][0]["results"]
|
|
345
|
+
assert len(results) == 2
|
|
346
|
+
paths = {
|
|
347
|
+
result["locations"][0]["physicalLocation"]["artifactLocation"]["uri"]
|
|
348
|
+
for result in results
|
|
349
|
+
}
|
|
350
|
+
assert paths == {"install.sh", "scripts/setup.sh"}
|
|
351
|
+
|
|
352
|
+
|
|
303
353
|
def test_write_github_step_summary(tmp_path):
|
|
304
354
|
summary_file = tmp_path / "step_summary.md"
|
|
305
355
|
r1 = SingleResult("debian:12-slim", DistroStatus.PASS, 0, 1.0)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{opsscript_gate-0.6.3 → opsscript_gate-0.7.0}/examples/alpine-incompatibility/apt_dependency.sh
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|