opsscript-gate 0.6.2__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.2 → opsscript_gate-0.7.0}/.github/workflow-templates/opsscript-gate.yml +1 -1
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/CHANGELOG.md +14 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/CITATION.cff +2 -2
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/PKG-INFO +46 -20
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/README.md +44 -18
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/README.zh-CN.md +13 -4
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/action.yml +2 -2
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/docs/configuration.md +7 -3
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/docs/gpt-oss.md +1 -1
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/examples/github-actions/README.md +1 -1
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/examples/github-actions/workflow.yml +1 -1
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/pyproject.toml +5 -1
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/src/opsscript_gate/__init__.py +1 -1
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/src/opsscript_gate/cli.py +51 -2
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/src/opsscript_gate/config.py +1 -1
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/src/opsscript_gate/reporter.py +120 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/tests/test_action_contract.py +1 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/tests/test_onboarding.py +22 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/tests/test_runner.py +50 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/.gitattributes +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/.github/ISSUE_TEMPLATE/distro_request.yml +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/.github/assets/social-preview.png +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/.github/pull_request_template.md +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/.github/workflow-templates/properties.json +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/.github/workflows/demo.yml +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/.github/workflows/release.yml +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/.github/workflows/test.yml +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/.gitignore +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/CONTRIBUTING.md +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/LICENSE +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/ROADMAP.md +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/SECURITY.md +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/docs/troubleshooting.md +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/examples/alpine-incompatibility/README.md +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/examples/alpine-incompatibility/apt_dependency.sh +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/examples/basic/README.md +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/examples/basic/clean_setup.sh +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/examples/interactive-hang/README.md +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/examples/interactive-hang/prompt_confirm.sh +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/src/opsscript_gate/discovery.py +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/src/opsscript_gate/models.py +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/src/opsscript_gate/remediation.py +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/src/opsscript_gate/runner.py +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/tests/fixtures/fail_deps.sh +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/tests/fixtures/fail_hang.sh +0 -0
- {opsscript_gate-0.6.2 → opsscript_gate-0.7.0}/tests/fixtures/pass_basic.sh +0 -0
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
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
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Added `opsscript-gate doctor` to diagnose Python and Docker prerequisites before a local run.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- Expanded package and Marketplace keywords for shell installers, ShellCheck users, portability, and BusyBox.
|
|
15
|
+
- Clarified the Action description around shell installer runtime compatibility.
|
|
16
|
+
|
|
3
17
|
## [0.6.2] - 2026-10-01
|
|
4
18
|
|
|
5
19
|
### Changed
|
|
@@ -5,8 +5,8 @@ type: software
|
|
|
5
5
|
authors:
|
|
6
6
|
- family-names: Mresyzz
|
|
7
7
|
given-names: Mresy
|
|
8
|
-
version: 0.
|
|
9
|
-
date-released: 2026-
|
|
8
|
+
version: 0.7.0
|
|
9
|
+
date-released: 2026-10-01
|
|
10
10
|
repository-code: https://github.com/Mresyzz/opsscript-gate
|
|
11
11
|
url: https://github.com/Mresyzz/opsscript-gate
|
|
12
12
|
license: MIT
|
|
@@ -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
|
|
@@ -11,7 +11,7 @@ Project-URL: Documentation, https://github.com/Mresyzz/opsscript-gate/tree/main/
|
|
|
11
11
|
Author: Mresyzz
|
|
12
12
|
License: MIT
|
|
13
13
|
License-File: LICENSE
|
|
14
|
-
Keywords: alpine,bootstrap,ci,compatibility,cross-distro,debian,devops,docker,github-actions,installer,linux,posix,shell,shellcheck-alternative,testing,ubuntu
|
|
14
|
+
Keywords: alpine,bootstrap,busybox,ci,compatibility,cross-distro,debian,devops,docker,github-actions,installer,installer-testing,linux,posix,shell,shell-portability,shellcheck,shellcheck-alternative,testing,ubuntu
|
|
15
15
|
Classifier: Development Status :: 4 - Beta
|
|
16
16
|
Classifier: Intended Audience :: Developers
|
|
17
17
|
Classifier: Intended Audience :: System Administrators
|
|
@@ -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
|
|
@@ -64,6 +64,7 @@ were introduced in v0.5.0; projects on v0.4.1 and earlier do not include them.
|
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
66
|
opsscript-gate init
|
|
67
|
+
opsscript-gate doctor
|
|
67
68
|
opsscript-gate run --dry-run
|
|
68
69
|
opsscript-gate run --format json --output reports/compatibility.json
|
|
69
70
|
```
|
|
@@ -71,6 +72,7 @@ opsscript-gate run --format json --output reports/compatibility.json
|
|
|
71
72
|
`init` creates `.opsscript-gate.json` and a GitHub Actions workflow without replacing
|
|
72
73
|
existing files. Review its exclusions (`tests/*`, `examples/*`) and offline network
|
|
73
74
|
default before running. No Docker is required for `init` or `--dry-run`.
|
|
75
|
+
Use `opsscript-gate doctor` to check Python and Docker before a real run.
|
|
74
76
|
|
|
75
77
|
Useful for standalone installers, container entrypoints and release scripts that
|
|
76
78
|
must work on both GNU/Linux and Alpine/BusyBox. Each script runs independently;
|
|
@@ -84,8 +86,9 @@ See [configuration and migration](docs/configuration.md) and
|
|
|
84
86
|
|
|
85
87
|
Add this workflow to `.github/workflows/shell-compat.yml`:
|
|
86
88
|
|
|
87
|
-
|
|
88
|
-
|
|
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.
|
|
89
92
|
|
|
90
93
|
You can copy the maintained example from [`examples/github-actions/workflow.yml`](examples/github-actions/workflow.yml)
|
|
91
94
|
and change the `script-path`, or use the minimal workflow below.
|
|
@@ -99,7 +102,9 @@ jobs:
|
|
|
99
102
|
runs-on: ubuntu-latest
|
|
100
103
|
steps:
|
|
101
104
|
- uses: actions/checkout@v7
|
|
102
|
-
|
|
105
|
+
with:
|
|
106
|
+
persist-credentials: false
|
|
107
|
+
- uses: Mresyzz/opsscript-gate@v0.7.0
|
|
103
108
|
```
|
|
104
109
|
|
|
105
110
|
This zero-configuration form discovers shell scripts in the repository. Use
|
|
@@ -110,7 +115,7 @@ repository has a single installer. For self-hosted model installers, see the
|
|
|
110
115
|
Or test a specific script with custom execution modes:
|
|
111
116
|
|
|
112
117
|
```yaml
|
|
113
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
118
|
+
- uses: Mresyzz/opsscript-gate@v0.7.0
|
|
114
119
|
with:
|
|
115
120
|
script-path: scripts/install.sh
|
|
116
121
|
shell: auto
|
|
@@ -140,8 +145,9 @@ unrelated scripts:
|
|
|
140
145
|
```yaml
|
|
141
146
|
- uses: actions/checkout@v7
|
|
142
147
|
with:
|
|
148
|
+
persist-credentials: false
|
|
143
149
|
fetch-depth: 0
|
|
144
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
150
|
+
- uses: Mresyzz/opsscript-gate@v0.7.0
|
|
145
151
|
with:
|
|
146
152
|
changed-since: ${{ github.event.pull_request.base.sha }}
|
|
147
153
|
preset: minimal
|
|
@@ -218,6 +224,28 @@ The action also writes a markdown matrix to `$GITHUB_STEP_SUMMARY`:
|
|
|
218
224
|
</details>
|
|
219
225
|
```
|
|
220
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
|
+
|
|
221
249
|
---
|
|
222
250
|
|
|
223
251
|
## Example: runtime behavior that static analysis misses
|
|
@@ -353,6 +381,12 @@ Customize the matrix at any time via `--matrix` or Action input `matrix`.
|
|
|
353
381
|
|
|
354
382
|
## CLI reference
|
|
355
383
|
|
|
384
|
+
Check local prerequisites before starting containers:
|
|
385
|
+
|
|
386
|
+
```text
|
|
387
|
+
opsscript-gate doctor [--format table|json]
|
|
388
|
+
```
|
|
389
|
+
|
|
356
390
|
```text
|
|
357
391
|
usage: opsscript-gate run [-h] [--matrix MATRIX] [-j JOBS] [--timeout TIMEOUT]
|
|
358
392
|
[--format {table,markdown,json}]
|
|
@@ -368,7 +402,7 @@ usage: opsscript-gate run [-h] [--matrix MATRIX] [-j JOBS] [--timeout TIMEOUT]
|
|
|
368
402
|
| `-j, --jobs` | Integer | `min(2, size)` | Number of concurrent container jobs |
|
|
369
403
|
| `--matrix` | String | `debian:12-slim,ubuntu:22.04,ubuntu:24.04,alpine:3.20` | Comma-separated list of Docker images |
|
|
370
404
|
| `--timeout` | Integer | `60` | Hard timeout per container in seconds |
|
|
371
|
-
| `--format` | Choice | `table` | Output format: `table`, `markdown`, or `
|
|
405
|
+
| `--format` | Choice | `table` | Output format: `table`, `markdown`, `json`, or `sarif` |
|
|
372
406
|
| `--shell` | Choice | `posix` | Execution mode: `posix` (default), `shebang`, or `auto` |
|
|
373
407
|
| `--mem-limit` | String | `256m` | Memory limit per container (e.g. `256m`, `512m`) |
|
|
374
408
|
| `--pids-limit`| Integer | `128` | Maximum number of processes per container |
|
|
@@ -405,14 +439,6 @@ usage: opsscript-gate run [-h] [--matrix MATRIX] [-j JOBS] [--timeout TIMEOUT]
|
|
|
405
439
|
Explicit CLI options override project settings. Explicit script paths bypass discovery
|
|
406
440
|
and its exclusions. See [complete configuration semantics](docs/configuration.md).
|
|
407
441
|
|
|
408
|
-
## Downstream example
|
|
409
|
-
|
|
410
|
-
[`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.
|
|
411
|
-
|
|
412
|
-
See the downstream workflow: [`.github/workflows/test.yml`](https://github.com/Mresyzz/linux-dev-bootstrap/blob/main/.github/workflows/test.yml).
|
|
413
|
-
|
|
414
|
-
---
|
|
415
|
-
|
|
416
442
|
## 🧪 Development & Testing
|
|
417
443
|
|
|
418
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
|
|
@@ -34,6 +34,7 @@ were introduced in v0.5.0; projects on v0.4.1 and earlier do not include them.
|
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
36
|
opsscript-gate init
|
|
37
|
+
opsscript-gate doctor
|
|
37
38
|
opsscript-gate run --dry-run
|
|
38
39
|
opsscript-gate run --format json --output reports/compatibility.json
|
|
39
40
|
```
|
|
@@ -41,6 +42,7 @@ opsscript-gate run --format json --output reports/compatibility.json
|
|
|
41
42
|
`init` creates `.opsscript-gate.json` and a GitHub Actions workflow without replacing
|
|
42
43
|
existing files. Review its exclusions (`tests/*`, `examples/*`) and offline network
|
|
43
44
|
default before running. No Docker is required for `init` or `--dry-run`.
|
|
45
|
+
Use `opsscript-gate doctor` to check Python and Docker before a real run.
|
|
44
46
|
|
|
45
47
|
Useful for standalone installers, container entrypoints and release scripts that
|
|
46
48
|
must work on both GNU/Linux and Alpine/BusyBox. Each script runs independently;
|
|
@@ -54,8 +56,9 @@ See [configuration and migration](docs/configuration.md) and
|
|
|
54
56
|
|
|
55
57
|
Add this workflow to `.github/workflows/shell-compat.yml`:
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
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.
|
|
59
62
|
|
|
60
63
|
You can copy the maintained example from [`examples/github-actions/workflow.yml`](examples/github-actions/workflow.yml)
|
|
61
64
|
and change the `script-path`, or use the minimal workflow below.
|
|
@@ -69,7 +72,9 @@ jobs:
|
|
|
69
72
|
runs-on: ubuntu-latest
|
|
70
73
|
steps:
|
|
71
74
|
- uses: actions/checkout@v7
|
|
72
|
-
|
|
75
|
+
with:
|
|
76
|
+
persist-credentials: false
|
|
77
|
+
- uses: Mresyzz/opsscript-gate@v0.7.0
|
|
73
78
|
```
|
|
74
79
|
|
|
75
80
|
This zero-configuration form discovers shell scripts in the repository. Use
|
|
@@ -80,7 +85,7 @@ repository has a single installer. For self-hosted model installers, see the
|
|
|
80
85
|
Or test a specific script with custom execution modes:
|
|
81
86
|
|
|
82
87
|
```yaml
|
|
83
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
88
|
+
- uses: Mresyzz/opsscript-gate@v0.7.0
|
|
84
89
|
with:
|
|
85
90
|
script-path: scripts/install.sh
|
|
86
91
|
shell: auto
|
|
@@ -110,8 +115,9 @@ unrelated scripts:
|
|
|
110
115
|
```yaml
|
|
111
116
|
- uses: actions/checkout@v7
|
|
112
117
|
with:
|
|
118
|
+
persist-credentials: false
|
|
113
119
|
fetch-depth: 0
|
|
114
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
120
|
+
- uses: Mresyzz/opsscript-gate@v0.7.0
|
|
115
121
|
with:
|
|
116
122
|
changed-since: ${{ github.event.pull_request.base.sha }}
|
|
117
123
|
preset: minimal
|
|
@@ -188,6 +194,28 @@ The action also writes a markdown matrix to `$GITHUB_STEP_SUMMARY`:
|
|
|
188
194
|
</details>
|
|
189
195
|
```
|
|
190
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
|
+
|
|
191
219
|
---
|
|
192
220
|
|
|
193
221
|
## Example: runtime behavior that static analysis misses
|
|
@@ -323,6 +351,12 @@ Customize the matrix at any time via `--matrix` or Action input `matrix`.
|
|
|
323
351
|
|
|
324
352
|
## CLI reference
|
|
325
353
|
|
|
354
|
+
Check local prerequisites before starting containers:
|
|
355
|
+
|
|
356
|
+
```text
|
|
357
|
+
opsscript-gate doctor [--format table|json]
|
|
358
|
+
```
|
|
359
|
+
|
|
326
360
|
```text
|
|
327
361
|
usage: opsscript-gate run [-h] [--matrix MATRIX] [-j JOBS] [--timeout TIMEOUT]
|
|
328
362
|
[--format {table,markdown,json}]
|
|
@@ -338,7 +372,7 @@ usage: opsscript-gate run [-h] [--matrix MATRIX] [-j JOBS] [--timeout TIMEOUT]
|
|
|
338
372
|
| `-j, --jobs` | Integer | `min(2, size)` | Number of concurrent container jobs |
|
|
339
373
|
| `--matrix` | String | `debian:12-slim,ubuntu:22.04,ubuntu:24.04,alpine:3.20` | Comma-separated list of Docker images |
|
|
340
374
|
| `--timeout` | Integer | `60` | Hard timeout per container in seconds |
|
|
341
|
-
| `--format` | Choice | `table` | Output format: `table`, `markdown`, or `
|
|
375
|
+
| `--format` | Choice | `table` | Output format: `table`, `markdown`, `json`, or `sarif` |
|
|
342
376
|
| `--shell` | Choice | `posix` | Execution mode: `posix` (default), `shebang`, or `auto` |
|
|
343
377
|
| `--mem-limit` | String | `256m` | Memory limit per container (e.g. `256m`, `512m`) |
|
|
344
378
|
| `--pids-limit`| Integer | `128` | Maximum number of processes per container |
|
|
@@ -375,14 +409,6 @@ usage: opsscript-gate run [-h] [--matrix MATRIX] [-j JOBS] [--timeout TIMEOUT]
|
|
|
375
409
|
Explicit CLI options override project settings. Explicit script paths bypass discovery
|
|
376
410
|
and its exclusions. See [complete configuration semantics](docs/configuration.md).
|
|
377
411
|
|
|
378
|
-
## Downstream example
|
|
379
|
-
|
|
380
|
-
[`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.
|
|
381
|
-
|
|
382
|
-
See the downstream workflow: [`.github/workflows/test.yml`](https://github.com/Mresyzz/linux-dev-bootstrap/blob/main/.github/workflows/test.yml).
|
|
383
|
-
|
|
384
|
-
---
|
|
385
|
-
|
|
386
412
|
## 🧪 Development & Testing
|
|
387
413
|
|
|
388
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 .
|
|
@@ -23,13 +23,19 @@ opsscript-gate run examples/basic/clean_setup.sh --dry-run
|
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
25
|
opsscript-gate init
|
|
26
|
+
opsscript-gate doctor
|
|
26
27
|
opsscript-gate run --dry-run
|
|
27
28
|
opsscript-gate run --format json --output reports/compatibility.json
|
|
28
29
|
```
|
|
29
30
|
|
|
30
|
-
`init` 生成配置和 GitHub 工作流,不覆盖已有文件。工作流引用 `v0.
|
|
31
|
+
`init` 生成配置和 GitHub 工作流,不覆盖已有文件。工作流引用 `v0.7.0`。
|
|
31
32
|
预览不需要 Docker;真实运行需要 Python 3.10+
|
|
32
33
|
和可访问的 Linux Docker 引擎。
|
|
34
|
+
`opsscript-gate doctor` 可以在真实运行前检查 Python 和 Docker。
|
|
35
|
+
|
|
36
|
+
需要接入 GitHub Code Scanning 时,将 `--format sarif --output reports/opsscript-gate.sarif`
|
|
37
|
+
传给运行命令,再用 `github/codeql-action/upload-sarif@v3` 上传报告。每个失败发行版
|
|
38
|
+
都会保留脚本路径、诊断行号、命令和退出码;全部通过时仍会生成合法的空 SARIF 报告。
|
|
33
39
|
|
|
34
40
|
生成的配置默认排除 `tests/*`、`examples/*`,使用 Debian + Alpine,尊重
|
|
35
41
|
脚本 shebang,并关闭网络。请根据实际脚本审核配置;需要下载文件时启用
|
|
@@ -47,13 +53,16 @@ opsscript-gate run --format json --output reports/compatibility.json
|
|
|
47
53
|
返回码 `0` 表示全部通过,`1` 表示检查失败或发生错误。`--dry-run` 成功只表示
|
|
48
54
|
执行计划生成成功。容器不是不可信代码的安全沙箱。
|
|
49
55
|
|
|
56
|
+
需要排查本地环境时运行 `opsscript-gate doctor --format json`;它会检查 Python、
|
|
57
|
+
Docker daemon 和可选的项目配置。
|
|
58
|
+
|
|
50
59
|
在 Pull Request 中只检查本次修改的脚本,可以先获取完整 Git 历史,再传入目标分支提交:
|
|
51
60
|
|
|
52
61
|
```yaml
|
|
53
62
|
- uses: actions/checkout@v7
|
|
54
63
|
with:
|
|
55
64
|
fetch-depth: 0
|
|
56
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
65
|
+
- uses: Mresyzz/opsscript-gate@v0.7.0
|
|
57
66
|
with:
|
|
58
67
|
changed-since: ${{ github.event.pull_request.base.sha }}
|
|
59
68
|
preset: minimal
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name: 'OpsScript Gate'
|
|
2
|
-
description: 'Run Linux shell
|
|
2
|
+
description: 'Run Linux shell installers across Debian, Ubuntu, and Alpine; a ShellCheck companion for runtime compatibility in CI.'
|
|
3
3
|
author: 'Mresyzz'
|
|
4
4
|
branding:
|
|
5
5
|
icon: 'shield'
|
|
@@ -23,7 +23,7 @@ inputs:
|
|
|
23
23
|
required: false
|
|
24
24
|
default: ''
|
|
25
25
|
format:
|
|
26
|
-
description: 'Output report format: table, markdown, or
|
|
26
|
+
description: 'Output report format: table, markdown, json, or sarif'
|
|
27
27
|
required: false
|
|
28
28
|
default: ''
|
|
29
29
|
shell:
|
|
@@ -7,11 +7,15 @@ have these commands.
|
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
9
|
opsscript-gate init
|
|
10
|
+
opsscript-gate doctor
|
|
10
11
|
opsscript-gate run --dry-run
|
|
11
12
|
```
|
|
12
13
|
|
|
13
14
|
`init` refuses to overwrite either `.opsscript-gate.json` or
|
|
14
|
-
`.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
|
+
|
|
17
|
+
`doctor` checks Python and Docker connectivity without executing repository scripts.
|
|
18
|
+
Use `opsscript-gate doctor --format json` when collecting a support report.
|
|
15
19
|
|
|
16
20
|
The generated config uses the two-image `minimal` preset, honors recognized shell
|
|
17
21
|
shebangs, disables networking, and excludes `tests/*` and `examples/*`. Review these
|
|
@@ -95,7 +99,7 @@ For pull requests, set `changed-since` to the base commit and fetch full Git his
|
|
|
95
99
|
- uses: actions/checkout@v7
|
|
96
100
|
with:
|
|
97
101
|
fetch-depth: 0
|
|
98
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
102
|
+
- uses: Mresyzz/opsscript-gate@v0.7.0
|
|
99
103
|
with:
|
|
100
104
|
changed-since: ${{ github.event.pull_request.base.sha }}
|
|
101
105
|
preset: minimal
|
|
@@ -105,7 +109,7 @@ The Action passes when no changed shell scripts are selected. This keeps unrelat
|
|
|
105
109
|
documentation or application changes from starting container jobs.
|
|
106
110
|
|
|
107
111
|
```yaml
|
|
108
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
112
|
+
- uses: Mresyzz/opsscript-gate@v0.7.0
|
|
109
113
|
with:
|
|
110
114
|
config: .opsscript-gate.json
|
|
111
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" }
|
|
@@ -29,6 +29,10 @@ keywords = [
|
|
|
29
29
|
"ubuntu",
|
|
30
30
|
"alpine",
|
|
31
31
|
"shellcheck-alternative",
|
|
32
|
+
"shellcheck",
|
|
33
|
+
"shell-portability",
|
|
34
|
+
"installer-testing",
|
|
35
|
+
"busybox",
|
|
32
36
|
]
|
|
33
37
|
classifiers = [
|
|
34
38
|
"Development Status :: 4 - Beta",
|
|
@@ -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
|
)
|
|
@@ -24,6 +26,7 @@ from opsscript_gate.runner import (
|
|
|
24
26
|
DEFAULT_MATRIX,
|
|
25
27
|
DEFAULT_TIMEOUT,
|
|
26
28
|
DockerDaemonError,
|
|
29
|
+
get_docker_client,
|
|
27
30
|
run_matrix,
|
|
28
31
|
)
|
|
29
32
|
|
|
@@ -43,6 +46,16 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
43
46
|
|
|
44
47
|
subparsers = parser.add_subparsers(dest="command", help="Available subcommands")
|
|
45
48
|
subparsers.add_parser("init", help="Create project configuration and GitHub workflow")
|
|
49
|
+
doctor_parser = subparsers.add_parser(
|
|
50
|
+
"doctor",
|
|
51
|
+
help="Check the local environment before running containers",
|
|
52
|
+
)
|
|
53
|
+
doctor_parser.add_argument(
|
|
54
|
+
"--format",
|
|
55
|
+
choices=["table", "json"],
|
|
56
|
+
default="table",
|
|
57
|
+
help="Output format: 'table' (default) or 'json'",
|
|
58
|
+
)
|
|
46
59
|
|
|
47
60
|
# 'run' subcommand
|
|
48
61
|
run_parser = subparsers.add_parser(
|
|
@@ -80,9 +93,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
80
93
|
)
|
|
81
94
|
run_parser.add_argument(
|
|
82
95
|
"--format",
|
|
83
|
-
choices=["table", "markdown", "json"],
|
|
96
|
+
choices=["table", "markdown", "json", "sarif"],
|
|
84
97
|
default="table",
|
|
85
|
-
help="Output report format: 'table' (default ASCII), 'markdown', or '
|
|
98
|
+
help="Output report format: 'table' (default ASCII), 'markdown', 'json', or 'sarif'",
|
|
86
99
|
)
|
|
87
100
|
run_parser.add_argument(
|
|
88
101
|
"--shell",
|
|
@@ -149,6 +162,38 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
149
162
|
sys.stderr.write(f"Error: {err}\n")
|
|
150
163
|
return 1
|
|
151
164
|
|
|
165
|
+
if args.command == "doctor":
|
|
166
|
+
checks = [
|
|
167
|
+
{
|
|
168
|
+
"name": "python",
|
|
169
|
+
"ok": sys.version_info >= (3, 10),
|
|
170
|
+
"detail": f"Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
try:
|
|
174
|
+
get_docker_client()
|
|
175
|
+
checks.append({"name": "docker", "ok": True, "detail": "Docker daemon is reachable"})
|
|
176
|
+
except DockerDaemonError as err:
|
|
177
|
+
checks.append({"name": "docker", "ok": False, "detail": str(err)})
|
|
178
|
+
|
|
179
|
+
config_path = Path(".opsscript-gate.json")
|
|
180
|
+
checks.append({
|
|
181
|
+
"name": "project",
|
|
182
|
+
"ok": True,
|
|
183
|
+
"detail": f"Found {config_path}" if config_path.is_file() else "No project config (optional)",
|
|
184
|
+
})
|
|
185
|
+
passed = all(check["ok"] for check in checks)
|
|
186
|
+
if args.format == "json":
|
|
187
|
+
print(json.dumps({"status": "PASS" if passed else "FAIL", "checks": checks}, indent=2))
|
|
188
|
+
else:
|
|
189
|
+
print("OpsScript Gate doctor")
|
|
190
|
+
for check in checks:
|
|
191
|
+
marker = "PASS" if check["ok"] else "FAIL"
|
|
192
|
+
print(f"[{marker}] {check['name']}: {check['detail']}")
|
|
193
|
+
if not passed:
|
|
194
|
+
print("Run `opsscript-gate init` after fixing the failed check, then preview with `opsscript-gate run --dry-run`.")
|
|
195
|
+
return 0 if passed else 1
|
|
196
|
+
|
|
152
197
|
if args.command == "run":
|
|
153
198
|
try:
|
|
154
199
|
config = load_config(args.config)
|
|
@@ -258,6 +303,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
258
303
|
# Format report output
|
|
259
304
|
if args.format == "json":
|
|
260
305
|
output = format_json(report)
|
|
306
|
+
elif args.format == "sarif":
|
|
307
|
+
output = format_sarif(report, script_path=script_path)
|
|
261
308
|
elif args.format == "markdown":
|
|
262
309
|
output = format_github_summary(report, script_path=script_path)
|
|
263
310
|
else:
|
|
@@ -310,6 +357,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
310
357
|
|
|
311
358
|
if args.format == "json":
|
|
312
359
|
output = format_json(multi_report)
|
|
360
|
+
elif args.format == "sarif":
|
|
361
|
+
output = format_multi_sarif(multi_report)
|
|
313
362
|
elif args.format == "markdown":
|
|
314
363
|
output = format_multi_github_summary(multi_report)
|
|
315
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
|
|
|
@@ -8,6 +8,7 @@ from opsscript_gate.cli import main
|
|
|
8
8
|
from opsscript_gate.config import CONFIG_NAME, init_project, load_config
|
|
9
9
|
from opsscript_gate.discovery import discover_changed_scripts, discover_scripts, is_shell_script
|
|
10
10
|
from opsscript_gate.models import RunReport, SingleResult, DistroStatus
|
|
11
|
+
from opsscript_gate.runner import DockerDaemonError
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
@pytest.fixture
|
|
@@ -35,6 +36,27 @@ def test_init_preview_and_no_overwrite(project, capsys):
|
|
|
35
36
|
assert Path(CONFIG_NAME).read_bytes() == original
|
|
36
37
|
|
|
37
38
|
|
|
39
|
+
def test_doctor_reports_local_prerequisites(project, capsys):
|
|
40
|
+
with patch("opsscript_gate.cli.get_docker_client"):
|
|
41
|
+
assert main(["doctor"]) == 0
|
|
42
|
+
output = capsys.readouterr().out
|
|
43
|
+
assert "OpsScript Gate doctor" in output
|
|
44
|
+
assert "[PASS] python" in output
|
|
45
|
+
assert "[PASS] docker" in output
|
|
46
|
+
assert "No project config (optional)" in output
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_doctor_json_reports_docker_failure(project, capsys):
|
|
50
|
+
with patch(
|
|
51
|
+
"opsscript_gate.cli.get_docker_client",
|
|
52
|
+
side_effect=DockerDaemonError("Docker is not running"),
|
|
53
|
+
):
|
|
54
|
+
assert main(["doctor", "--format", "json"]) == 1
|
|
55
|
+
report = json.loads(capsys.readouterr().out)
|
|
56
|
+
assert report["status"] == "FAIL"
|
|
57
|
+
assert next(check for check in report["checks"] if check["name"] == "docker")["ok"] is False
|
|
58
|
+
|
|
59
|
+
|
|
38
60
|
def test_init_existing_workflow_leaves_config_absent(project):
|
|
39
61
|
workflow = project / ".github/workflows/opsscript-gate.yml"
|
|
40
62
|
workflow.parent.mkdir(parents=True)
|
|
@@ -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.2 → 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
|