opsscript-gate 0.5.1__tar.gz → 0.6.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.5.1 → opsscript_gate-0.6.0}/CHANGELOG.md +6 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/PKG-INFO +31 -9
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/README.md +30 -8
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/README.zh-CN.md +23 -4
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/action.yml +6 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/docs/configuration.md +17 -2
- opsscript_gate-0.6.0/docs/gpt-oss.md +80 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/examples/github-actions/README.md +1 -1
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/examples/github-actions/workflow.yml +1 -1
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/pyproject.toml +1 -1
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/src/opsscript_gate/__init__.py +1 -1
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/src/opsscript_gate/cli.py +26 -2
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/src/opsscript_gate/config.py +1 -1
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/src/opsscript_gate/discovery.py +64 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/tests/test_action_contract.py +2 -1
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/tests/test_onboarding.py +35 -1
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/.gitattributes +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/.github/ISSUE_TEMPLATE/distro_request.yml +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/.github/assets/social-preview.png +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/.github/pull_request_template.md +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/.github/workflows/demo.yml +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/.github/workflows/release.yml +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/.github/workflows/test.yml +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/.gitignore +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/CONTRIBUTING.md +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/LICENSE +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/ROADMAP.md +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/SECURITY.md +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/docs/troubleshooting.md +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/examples/alpine-incompatibility/README.md +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/examples/alpine-incompatibility/apt_dependency.sh +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/examples/basic/README.md +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/examples/basic/clean_setup.sh +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/examples/interactive-hang/README.md +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/examples/interactive-hang/prompt_confirm.sh +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/src/opsscript_gate/models.py +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/src/opsscript_gate/remediation.py +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/src/opsscript_gate/reporter.py +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/src/opsscript_gate/runner.py +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/tests/fixtures/fail_deps.sh +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/tests/fixtures/fail_hang.sh +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/tests/fixtures/pass_basic.sh +0 -0
- {opsscript_gate-0.5.1 → opsscript_gate-0.6.0}/tests/test_runner.py +0 -0
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.0] - 2026-09-30
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Added `--changed-since` and the matching Action input to test only shell scripts changed since a Git revision.
|
|
7
|
+
- Changed-script selection uses the existing discovery safety checks and passes when a change contains no shell scripts.
|
|
8
|
+
|
|
3
9
|
## [0.5.1] - 2026-09-30
|
|
4
10
|
|
|
5
11
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: opsscript-gate
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.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://github.com/Mresyzz/opsscript-gate
|
|
6
6
|
Project-URL: Repository, https://github.com/Mresyzz/opsscript-gate
|
|
@@ -28,7 +28,7 @@ Description-Content-Type: text/markdown
|
|
|
28
28
|
|
|
29
29
|
# OpsScript Gate — shell script compatibility testing
|
|
30
30
|
|
|
31
|
-
[简体中文](README.zh-CN.md) · [Configuration](docs/configuration.md) · [Troubleshooting](docs/troubleshooting.md)
|
|
31
|
+
[简体中文](README.zh-CN.md) · [Configuration](docs/configuration.md) · [Troubleshooting](docs/troubleshooting.md) · [Self-hosted model installer guide](docs/gpt-oss.md)
|
|
32
32
|
|
|
33
33
|
> ShellCheck analyzes shell syntax. OpsScript Gate executes scripts in Linux containers.
|
|
34
34
|
|
|
@@ -47,11 +47,11 @@ It is designed for shell testing, portable shell validation, Bash/POSIX compatib
|
|
|
47
47
|
|
|
48
48
|
---
|
|
49
49
|
|
|
50
|
-
## Current release: v0.
|
|
50
|
+
## Current release: v0.6.0
|
|
51
51
|
|
|
52
52
|
The current release includes a project config file, a dry-run plan, presets, exclusions,
|
|
53
|
-
|
|
54
|
-
earlier do not include them.
|
|
53
|
+
saved reports, and changed-script selection for pull requests. The project controls
|
|
54
|
+
were introduced in v0.5.0; projects on v0.4.1 and earlier do not include them.
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
57
|
opsscript-gate init
|
|
@@ -87,16 +87,18 @@ jobs:
|
|
|
87
87
|
runs-on: ubuntu-latest
|
|
88
88
|
steps:
|
|
89
89
|
- uses: actions/checkout@v7
|
|
90
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
90
|
+
- uses: Mresyzz/opsscript-gate@v0.6.0
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
This zero-configuration form discovers shell scripts in the repository. Use
|
|
94
|
+
`opsscript-gate run --dry-run` to inspect the selection, or set `script-path` when the
|
|
95
|
+
repository has a single installer. For self-hosted model installers, see the
|
|
96
|
+
[installer guide](docs/gpt-oss.md).
|
|
95
97
|
|
|
96
98
|
Or test a specific script with custom execution modes:
|
|
97
99
|
|
|
98
100
|
```yaml
|
|
99
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
101
|
+
- uses: Mresyzz/opsscript-gate@v0.6.0
|
|
100
102
|
with:
|
|
101
103
|
script-path: scripts/install.sh
|
|
102
104
|
shell: auto
|
|
@@ -118,6 +120,25 @@ opsscript-gate run ./scripts/install.sh
|
|
|
118
120
|
opsscript-gate run
|
|
119
121
|
```
|
|
120
122
|
|
|
123
|
+
### Test only scripts changed in a pull request
|
|
124
|
+
|
|
125
|
+
Fetch the comparison revision and pass the pull request base SHA to avoid running
|
|
126
|
+
unrelated scripts:
|
|
127
|
+
|
|
128
|
+
```yaml
|
|
129
|
+
- uses: actions/checkout@v7
|
|
130
|
+
with:
|
|
131
|
+
fetch-depth: 0
|
|
132
|
+
- uses: Mresyzz/opsscript-gate@v0.6.0
|
|
133
|
+
with:
|
|
134
|
+
changed-since: ${{ github.event.pull_request.base.sha }}
|
|
135
|
+
preset: minimal
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
If the change does not include a shell script, the check passes without starting a
|
|
139
|
+
container. The same selection can be previewed locally with
|
|
140
|
+
`opsscript-gate run --changed-since origin/main --dry-run`.
|
|
141
|
+
|
|
121
142
|
### Example: package-manager mismatch
|
|
122
143
|
|
|
123
144
|
Create `install.sh`:
|
|
@@ -340,6 +361,7 @@ usage: opsscript-gate run [-h] [--matrix MATRIX] [-j JOBS] [--timeout TIMEOUT]
|
|
|
340
361
|
| `--mem-limit` | String | `256m` | Memory limit per container (e.g. `256m`, `512m`) |
|
|
341
362
|
| `--pids-limit`| Integer | `128` | Maximum number of processes per container |
|
|
342
363
|
| `--network` | Choice | `bridge` | Container network mode: `bridge` or `none` |
|
|
364
|
+
| `--changed-since` | Git revision | - | Test shell scripts changed between this revision and `HEAD` |
|
|
343
365
|
| `--version` | Flag | - | Show version number |
|
|
344
366
|
| `-h, --help` | Flag | - | Show argument help |
|
|
345
367
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OpsScript Gate — shell script compatibility testing
|
|
2
2
|
|
|
3
|
-
[简体中文](README.zh-CN.md) · [Configuration](docs/configuration.md) · [Troubleshooting](docs/troubleshooting.md)
|
|
3
|
+
[简体中文](README.zh-CN.md) · [Configuration](docs/configuration.md) · [Troubleshooting](docs/troubleshooting.md) · [Self-hosted model installer guide](docs/gpt-oss.md)
|
|
4
4
|
|
|
5
5
|
> ShellCheck analyzes shell syntax. OpsScript Gate executes scripts in Linux containers.
|
|
6
6
|
|
|
@@ -19,11 +19,11 @@ It is designed for shell testing, portable shell validation, Bash/POSIX compatib
|
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
-
## Current release: v0.
|
|
22
|
+
## Current release: v0.6.0
|
|
23
23
|
|
|
24
24
|
The current release includes a project config file, a dry-run plan, presets, exclusions,
|
|
25
|
-
|
|
26
|
-
earlier do not include them.
|
|
25
|
+
saved reports, and changed-script selection for pull requests. The project controls
|
|
26
|
+
were introduced in v0.5.0; projects on v0.4.1 and earlier do not include them.
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
29
|
opsscript-gate init
|
|
@@ -59,16 +59,18 @@ jobs:
|
|
|
59
59
|
runs-on: ubuntu-latest
|
|
60
60
|
steps:
|
|
61
61
|
- uses: actions/checkout@v7
|
|
62
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
62
|
+
- uses: Mresyzz/opsscript-gate@v0.6.0
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
This zero-configuration form discovers shell scripts in the repository. Use
|
|
66
|
+
`opsscript-gate run --dry-run` to inspect the selection, or set `script-path` when the
|
|
67
|
+
repository has a single installer. For self-hosted model installers, see the
|
|
68
|
+
[installer guide](docs/gpt-oss.md).
|
|
67
69
|
|
|
68
70
|
Or test a specific script with custom execution modes:
|
|
69
71
|
|
|
70
72
|
```yaml
|
|
71
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
73
|
+
- uses: Mresyzz/opsscript-gate@v0.6.0
|
|
72
74
|
with:
|
|
73
75
|
script-path: scripts/install.sh
|
|
74
76
|
shell: auto
|
|
@@ -90,6 +92,25 @@ opsscript-gate run ./scripts/install.sh
|
|
|
90
92
|
opsscript-gate run
|
|
91
93
|
```
|
|
92
94
|
|
|
95
|
+
### Test only scripts changed in a pull request
|
|
96
|
+
|
|
97
|
+
Fetch the comparison revision and pass the pull request base SHA to avoid running
|
|
98
|
+
unrelated scripts:
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
- uses: actions/checkout@v7
|
|
102
|
+
with:
|
|
103
|
+
fetch-depth: 0
|
|
104
|
+
- uses: Mresyzz/opsscript-gate@v0.6.0
|
|
105
|
+
with:
|
|
106
|
+
changed-since: ${{ github.event.pull_request.base.sha }}
|
|
107
|
+
preset: minimal
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
If the change does not include a shell script, the check passes without starting a
|
|
111
|
+
container. The same selection can be previewed locally with
|
|
112
|
+
`opsscript-gate run --changed-since origin/main --dry-run`.
|
|
113
|
+
|
|
93
114
|
### Example: package-manager mismatch
|
|
94
115
|
|
|
95
116
|
Create `install.sh`:
|
|
@@ -312,6 +333,7 @@ usage: opsscript-gate run [-h] [--matrix MATRIX] [-j JOBS] [--timeout TIMEOUT]
|
|
|
312
333
|
| `--mem-limit` | String | `256m` | Memory limit per container (e.g. `256m`, `512m`) |
|
|
313
334
|
| `--pids-limit`| Integer | `128` | Maximum number of processes per container |
|
|
314
335
|
| `--network` | Choice | `bridge` | Container network mode: `bridge` or `none` |
|
|
336
|
+
| `--changed-since` | Git revision | - | Test shell scripts changed between this revision and `HEAD` |
|
|
315
337
|
| `--version` | Flag | - | Show version number |
|
|
316
338
|
| `-h, --help` | Flag | - | Show argument help |
|
|
317
339
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OpsScript Gate:Shell 脚本跨发行版运行检查
|
|
2
2
|
|
|
3
|
-
[English](README.md) · [配置说明](docs/configuration.md) · [常见问题](docs/troubleshooting.md)
|
|
3
|
+
[English](README.md) · [配置说明](docs/configuration.md) · [常见问题](docs/troubleshooting.md) · [自托管模型安装脚本指南](docs/gpt-oss.md)
|
|
4
4
|
|
|
5
5
|
在 Debian、Ubuntu、Alpine 容器里实际运行 Shell 脚本,提前发现缺失命令、
|
|
6
6
|
Bash 依赖、包管理器假设和交互式阻塞。它与 ShellCheck 互补。
|
|
@@ -8,9 +8,9 @@ Bash 依赖、包管理器假设和交互式阻塞。它与 ShellCheck 互补。
|
|
|
8
8
|
适合独立安装脚本、容器入口脚本和发布脚本。每次只挂载目标脚本,
|
|
9
9
|
不挂载整个仓库;依赖其他仓库文件的脚本需要现有项目测试配合。
|
|
10
10
|
|
|
11
|
-
## 当前版本 v0.
|
|
11
|
+
## 当前版本 v0.6.0
|
|
12
12
|
|
|
13
|
-
v0.
|
|
13
|
+
v0.6.0 已作为正式版本发布;如果你要从源码验证当前分支,也可以执行:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
pip install -e .
|
|
@@ -25,7 +25,7 @@ opsscript-gate run --dry-run
|
|
|
25
25
|
opsscript-gate run --format json --output reports/compatibility.json
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
`init` 生成配置和 GitHub 工作流,不覆盖已有文件。工作流引用 `v0.
|
|
28
|
+
`init` 生成配置和 GitHub 工作流,不覆盖已有文件。工作流引用 `v0.6.0`。
|
|
29
29
|
预览不需要 Docker;真实运行需要 Python 3.10+
|
|
30
30
|
和可访问的 Linux Docker 引擎。
|
|
31
31
|
|
|
@@ -45,6 +45,25 @@ opsscript-gate run --format json --output reports/compatibility.json
|
|
|
45
45
|
返回码 `0` 表示全部通过,`1` 表示检查失败或发生错误。`--dry-run` 成功只表示
|
|
46
46
|
执行计划生成成功。容器不是不可信代码的安全沙箱。
|
|
47
47
|
|
|
48
|
+
在 Pull Request 中只检查本次修改的脚本,可以先获取完整 Git 历史,再传入目标分支提交:
|
|
49
|
+
|
|
50
|
+
```yaml
|
|
51
|
+
- uses: actions/checkout@v7
|
|
52
|
+
with:
|
|
53
|
+
fetch-depth: 0
|
|
54
|
+
- uses: Mresyzz/opsscript-gate@v0.6.0
|
|
55
|
+
with:
|
|
56
|
+
changed-since: ${{ github.event.pull_request.base.sha }}
|
|
57
|
+
preset: minimal
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
如果本次修改没有 Shell 脚本,检查会直接通过,不启动容器。 本地可以用
|
|
61
|
+
`opsscript-gate run --changed-since origin/main --dry-run` 预览选择结果。
|
|
62
|
+
|
|
63
|
+
如果仓库维护自托管模型的安装或启动脚本,可以参考
|
|
64
|
+
[自托管模型安装脚本指南](docs/gpt-oss.md)。它只验证 Shell 和发行版兼容性,
|
|
65
|
+
不代替 GPU、模型权重或推理质量测试。
|
|
66
|
+
|
|
48
67
|
## 示例:为什么 Alpine 上会失败
|
|
49
68
|
|
|
50
69
|
```sh
|
|
@@ -67,6 +67,10 @@ inputs:
|
|
|
67
67
|
description: 'Save a report file for upload-artifact or other tooling'
|
|
68
68
|
required: false
|
|
69
69
|
default: ''
|
|
70
|
+
changed-since:
|
|
71
|
+
description: 'Only test shell scripts changed between this Git revision and HEAD'
|
|
72
|
+
required: false
|
|
73
|
+
default: ''
|
|
70
74
|
|
|
71
75
|
runs:
|
|
72
76
|
using: 'composite'
|
|
@@ -99,6 +103,7 @@ runs:
|
|
|
99
103
|
INPUT_MAX_SCRIPTS: ${{ inputs.max-scripts }}
|
|
100
104
|
INPUT_DRY_RUN: ${{ inputs.dry-run }}
|
|
101
105
|
INPUT_OUTPUT: ${{ inputs.output }}
|
|
106
|
+
INPUT_CHANGED_SINCE: ${{ inputs.changed-since }}
|
|
102
107
|
run: |
|
|
103
108
|
ARGS=("run")
|
|
104
109
|
if [ -n "$INPUT_SCRIPT_PATH" ]; then
|
|
@@ -117,6 +122,7 @@ runs:
|
|
|
117
122
|
[ -z "$INPUT_PRESET" ] || ARGS+=(--preset "$INPUT_PRESET")
|
|
118
123
|
[ -z "$INPUT_MAX_SCRIPTS" ] || ARGS+=(--max-scripts "$INPUT_MAX_SCRIPTS")
|
|
119
124
|
[ -z "$INPUT_OUTPUT" ] || ARGS+=(--output "$INPUT_OUTPUT")
|
|
125
|
+
[ -z "$INPUT_CHANGED_SINCE" ] || ARGS+=(--changed-since "$INPUT_CHANGED_SINCE")
|
|
120
126
|
while IFS= read -r pattern; do
|
|
121
127
|
pattern="${pattern%$'\r'}"
|
|
122
128
|
[ -z "$pattern" ] || ARGS+=("--exclude" "$pattern")
|
|
@@ -11,7 +11,7 @@ opsscript-gate run --dry-run
|
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
`init` refuses to overwrite either `.opsscript-gate.json` or
|
|
14
|
-
`.github/workflows/opsscript-gate.yml`. The generated workflow uses the v0.
|
|
14
|
+
`.github/workflows/opsscript-gate.yml`. The generated workflow uses the v0.6.0 tag.
|
|
15
15
|
|
|
16
16
|
The generated config uses the two-image `minimal` preset, honors recognized shell
|
|
17
17
|
shebangs, disables networking, and excludes `tests/*` and `examples/*`. Review these
|
|
@@ -89,8 +89,23 @@ test result. Docker startup failures may occur before a report can be produced.
|
|
|
89
89
|
The Action accepts matching `config`, `preset`, `max-scripts`, `dry-run`, and `output`
|
|
90
90
|
inputs; `exclude` is a multiline list. Omitted execution inputs preserve config values.
|
|
91
91
|
|
|
92
|
+
For pull requests, set `changed-since` to the base commit and fetch full Git history:
|
|
93
|
+
|
|
94
|
+
```yaml
|
|
95
|
+
- uses: actions/checkout@v7
|
|
96
|
+
with:
|
|
97
|
+
fetch-depth: 0
|
|
98
|
+
- uses: Mresyzz/opsscript-gate@v0.6.0
|
|
99
|
+
with:
|
|
100
|
+
changed-since: ${{ github.event.pull_request.base.sha }}
|
|
101
|
+
preset: minimal
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The Action passes when no changed shell scripts are selected. This keeps unrelated
|
|
105
|
+
documentation or application changes from starting container jobs.
|
|
106
|
+
|
|
92
107
|
```yaml
|
|
93
|
-
- uses: Mresyzz/opsscript-gate@v0.
|
|
108
|
+
- uses: Mresyzz/opsscript-gate@v0.6.0
|
|
94
109
|
with:
|
|
95
110
|
config: .opsscript-gate.json
|
|
96
111
|
format: json
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Test self-hosted model installer scripts
|
|
2
|
+
|
|
3
|
+
OpsScript Gate fits the installation and release layer of a self-hosted model project. It
|
|
4
|
+
executes shell scripts in Debian, Ubuntu, and Alpine containers to catch distribution
|
|
5
|
+
differences, missing commands, package-manager assumptions, and interactive hangs.
|
|
6
|
+
|
|
7
|
+
This guide uses [OpenAI's gpt-oss open-weight models](https://openai.com/index/introducing-gpt-oss/)
|
|
8
|
+
as a concrete example. The checks cover installer scripts, startup scripts, and container
|
|
9
|
+
entrypoints. They do **not** test GPU drivers, CUDA/ROCm, model weights, inference speed,
|
|
10
|
+
output quality, or the memory requirements of a particular machine. Keeping this boundary
|
|
11
|
+
explicit prevents a script-compatibility check from being mistaken for a complete model
|
|
12
|
+
deployment test.
|
|
13
|
+
|
|
14
|
+
## Check changed scripts in pull requests
|
|
15
|
+
|
|
16
|
+
Assume the repository contains `scripts/bootstrap-gpt-oss.sh` or another shell installer:
|
|
17
|
+
|
|
18
|
+
```yaml
|
|
19
|
+
name: Model installer compatibility
|
|
20
|
+
|
|
21
|
+
on:
|
|
22
|
+
pull_request:
|
|
23
|
+
|
|
24
|
+
permissions:
|
|
25
|
+
contents: read
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
shell-runtime:
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v7
|
|
32
|
+
with:
|
|
33
|
+
fetch-depth: 0
|
|
34
|
+
persist-credentials: false
|
|
35
|
+
|
|
36
|
+
- uses: Mresyzz/opsscript-gate@v0.6.0
|
|
37
|
+
with:
|
|
38
|
+
changed-since: ${{ github.event.pull_request.base.sha }}
|
|
39
|
+
preset: minimal
|
|
40
|
+
shell: auto
|
|
41
|
+
network: none
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`changed-since` selects only shell files changed in the commit range. Documentation,
|
|
45
|
+
Python, model configuration, and frontend changes do not start containers. If no shell
|
|
46
|
+
file changed, the check passes without doing container work.
|
|
47
|
+
|
|
48
|
+
If the installer must download packages or model files, change `network` to `bridge` and
|
|
49
|
+
keep downloads in a dedicated test script. Once network access is enabled, upstream
|
|
50
|
+
mirrors and package indexes can affect repeatability.
|
|
51
|
+
|
|
52
|
+
## Preview and run locally
|
|
53
|
+
|
|
54
|
+
Confirm the selection before starting Docker:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install opsscript-gate
|
|
58
|
+
opsscript-gate run --changed-since origin/main --preset minimal --shell auto --dry-run
|
|
59
|
+
opsscript-gate run scripts/bootstrap-gpt-oss.sh --preset minimal --shell auto --network none
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
If an installer depends on other repository files, an explicit `script-path` still checks
|
|
63
|
+
only that script; dependencies are not mounted into the container. Put the required logic
|
|
64
|
+
behind a standalone test entrypoint, or use the project's integration tests for file-level
|
|
65
|
+
collaboration and use OpsScript Gate for the cross-distribution runtime layer.
|
|
66
|
+
|
|
67
|
+
## What to inspect when a check fails
|
|
68
|
+
|
|
69
|
+
- `apt-get` is missing on Alpine: choose `apk` for that distribution, or detect it and
|
|
70
|
+
return a clear message.
|
|
71
|
+
- `bash` is missing: declare `#!/usr/bin/env bash` for Bash syntax, use `shell: auto`, and
|
|
72
|
+
confirm that the selected test image includes Bash.
|
|
73
|
+
- The script waits for input: remove interactive prompts or return a clear non-interactive
|
|
74
|
+
error.
|
|
75
|
+
- A download fails: separate script failures from network or package-index instability;
|
|
76
|
+
use `network: bridge` only when needed and pin versions with checksums.
|
|
77
|
+
|
|
78
|
+
Model loading, GPU drivers, and inference quality belong in the project's hardware
|
|
79
|
+
integration tests. OpsScript Gate makes the installer and startup scripts pass a small,
|
|
80
|
+
repeatable shell-runtime check before those expensive tests run.
|
|
@@ -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.6.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.6.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" }
|
|
@@ -8,7 +8,7 @@ import time
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
10
|
from opsscript_gate import __version__
|
|
11
|
-
from opsscript_gate.discovery import discover_scripts
|
|
11
|
+
from opsscript_gate.discovery import discover_changed_scripts, discover_scripts
|
|
12
12
|
from opsscript_gate.config import DEFAULTS, PRESETS, init_project, load_config
|
|
13
13
|
from opsscript_gate.models import MultiScriptReport, RunReport
|
|
14
14
|
from opsscript_gate.reporter import (
|
|
@@ -108,6 +108,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
108
108
|
default="bridge",
|
|
109
109
|
help="Network mode for containers: 'bridge' or 'none' (default: bridge)",
|
|
110
110
|
)
|
|
111
|
+
run_parser.add_argument(
|
|
112
|
+
"--changed-since",
|
|
113
|
+
help="Only test shell scripts changed between this Git revision and HEAD",
|
|
114
|
+
)
|
|
111
115
|
|
|
112
116
|
run_parser.add_argument("--config", help="Project JSON configuration (default: .opsscript-gate.json)")
|
|
113
117
|
run_parser.add_argument("--preset", choices=list(PRESETS), help="Named distribution matrix")
|
|
@@ -172,18 +176,30 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
172
176
|
sys.stderr.write(f"Error: {err}\n")
|
|
173
177
|
return 1
|
|
174
178
|
# Resolve target script(s)
|
|
179
|
+
if args.script_path and args.changed_since:
|
|
180
|
+
sys.stderr.write("Error: use either a script path or --changed-since, not both\n")
|
|
181
|
+
return 1
|
|
182
|
+
changed_selection = bool(args.changed_since)
|
|
175
183
|
if args.script_path:
|
|
176
184
|
if not os.path.isfile(args.script_path):
|
|
177
185
|
sys.stderr.write(f"Error: Script file not found: {args.script_path}\n")
|
|
178
186
|
return 1
|
|
179
187
|
target_scripts = [args.script_path]
|
|
188
|
+
elif changed_selection:
|
|
189
|
+
try:
|
|
190
|
+
target_scripts = discover_changed_scripts(
|
|
191
|
+
".", since=args.changed_since, max_scripts=args.max_scripts, exclude=args.exclude
|
|
192
|
+
)
|
|
193
|
+
except ValueError as err:
|
|
194
|
+
sys.stderr.write(f"Error: {err}\n")
|
|
195
|
+
return 1
|
|
180
196
|
else:
|
|
181
197
|
try:
|
|
182
198
|
target_scripts = discover_scripts(".", max_scripts=args.max_scripts, exclude=args.exclude)
|
|
183
199
|
except ValueError as err:
|
|
184
200
|
sys.stderr.write(f"Error: {err}\n")
|
|
185
201
|
return 1
|
|
186
|
-
if not target_scripts:
|
|
202
|
+
if not target_scripts and not changed_selection:
|
|
187
203
|
sys.stderr.write(
|
|
188
204
|
"No shell scripts discovered in the current directory. "
|
|
189
205
|
"Please specify a script path explicitly.\n"
|
|
@@ -203,6 +219,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
203
219
|
plan = {"dry_run": True, "scripts": target_scripts, "matrix": matrix,
|
|
204
220
|
"executions": len(target_scripts) * len(matrix), "shell": args.shell,
|
|
205
221
|
"network": args.network, "timeout": args.timeout}
|
|
222
|
+
if changed_selection:
|
|
223
|
+
plan["changed_since"] = args.changed_since
|
|
206
224
|
output = json.dumps(plan, indent=2) if args.format == "json" else (
|
|
207
225
|
"Execution preview (no scripts executed)\n"
|
|
208
226
|
+ "\n".join(f" {path}" for path in target_scripts)
|
|
@@ -211,6 +229,12 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
211
229
|
)
|
|
212
230
|
return 0 if publish_output(output, args.output) else 1
|
|
213
231
|
|
|
232
|
+
if not target_scripts:
|
|
233
|
+
output = (
|
|
234
|
+
f"No changed shell scripts since {args.changed_since}; skipping compatibility checks."
|
|
235
|
+
)
|
|
236
|
+
return 0 if publish_output(output, args.output) else 1
|
|
237
|
+
|
|
214
238
|
if len(target_scripts) == 1:
|
|
215
239
|
script_path = target_scripts[0]
|
|
216
240
|
try:
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import fnmatch
|
|
5
|
+
import subprocess
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
7
8
|
# Directories to ignore during automatic script discovery
|
|
@@ -117,3 +118,66 @@ def discover_scripts(
|
|
|
117
118
|
# Sort alphabetically for stable, deterministic ordering
|
|
118
119
|
discovered.sort()
|
|
119
120
|
return discovered
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def discover_changed_scripts(
|
|
124
|
+
root_dir: str = ".",
|
|
125
|
+
since: str = "HEAD^",
|
|
126
|
+
max_scripts: int = MAX_DISCOVERED_SCRIPTS,
|
|
127
|
+
max_size_bytes: int = MAX_SCRIPT_SIZE_BYTES,
|
|
128
|
+
exclude: list[str] | None = None,
|
|
129
|
+
) -> list[str]:
|
|
130
|
+
"""Discover shell scripts changed between ``since`` and ``HEAD``.
|
|
131
|
+
|
|
132
|
+
Git provides the candidate paths; the same file, size, shebang, exclusion,
|
|
133
|
+
and overflow checks used by normal discovery still apply. A missing Git
|
|
134
|
+
repository or unavailable revision is reported as a user-facing error.
|
|
135
|
+
"""
|
|
136
|
+
root_path = Path(root_dir).resolve()
|
|
137
|
+
if not since or not since.strip() or since.startswith("-") or any(char.isspace() for char in since):
|
|
138
|
+
raise ValueError("changed-since must name a Git revision")
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
result = subprocess.run(
|
|
142
|
+
["git", "diff", "--name-only", "--diff-filter=ACMR", "-z",
|
|
143
|
+
f"{since}...HEAD", "--"],
|
|
144
|
+
cwd=root_path,
|
|
145
|
+
check=True,
|
|
146
|
+
stdout=subprocess.PIPE,
|
|
147
|
+
stderr=subprocess.PIPE,
|
|
148
|
+
)
|
|
149
|
+
except FileNotFoundError as exc:
|
|
150
|
+
raise ValueError("changed-since requires Git to be installed") from exc
|
|
151
|
+
except subprocess.CalledProcessError as exc:
|
|
152
|
+
detail = exc.stderr.decode("utf-8", errors="replace").strip()
|
|
153
|
+
raise ValueError(
|
|
154
|
+
f"Cannot compare Git revision {since!r} with HEAD"
|
|
155
|
+
+ (f": {detail}" if detail else "")
|
|
156
|
+
) from exc
|
|
157
|
+
|
|
158
|
+
changed_paths = [
|
|
159
|
+
item.decode("utf-8", errors="surrogateescape")
|
|
160
|
+
for item in result.stdout.split(b"\0")
|
|
161
|
+
if item
|
|
162
|
+
]
|
|
163
|
+
discovered: list[str] = []
|
|
164
|
+
for relative_name in changed_paths:
|
|
165
|
+
candidate = root_path / Path(relative_name)
|
|
166
|
+
try:
|
|
167
|
+
resolved = candidate.resolve()
|
|
168
|
+
rel_path = resolved.relative_to(root_path).as_posix()
|
|
169
|
+
except (OSError, ValueError):
|
|
170
|
+
continue
|
|
171
|
+
if any(fnmatch.fnmatchcase(rel_path, pattern) for pattern in (exclude or [])):
|
|
172
|
+
continue
|
|
173
|
+
if not is_shell_script(candidate, max_size_bytes=max_size_bytes):
|
|
174
|
+
continue
|
|
175
|
+
normalized = rel_path if "/" in rel_path else f"./{rel_path}"
|
|
176
|
+
discovered.append(normalized)
|
|
177
|
+
if len(discovered) > max_scripts:
|
|
178
|
+
raise ValueError(
|
|
179
|
+
f"Changed files include more than {max_scripts} shell scripts, exceeding limit. "
|
|
180
|
+
"Use --exclude or increase --max-scripts; no scripts were executed."
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
return sorted(set(discovered))
|
|
@@ -7,13 +7,14 @@ ROOT = Path(__file__).parents[1]
|
|
|
7
7
|
def test_action_metadata_is_valid_and_inputs_are_wired():
|
|
8
8
|
text = (ROOT / "action.yml").read_text(encoding="utf-8")
|
|
9
9
|
assert "using: 'composite'" in text
|
|
10
|
-
for name in ("config", "preset", "exclude", "max-scripts", "dry-run", "output"):
|
|
10
|
+
for name in ("config", "preset", "exclude", "max-scripts", "dry-run", "output", "changed-since"):
|
|
11
11
|
assert f" {name}:" in text
|
|
12
12
|
run_text = text
|
|
13
13
|
for flag in ("--timeout", "--format", "--shell", "--mem-limit", "--pids-limit",
|
|
14
14
|
"--network", "--matrix", "--jobs", "--config", "--preset",
|
|
15
15
|
"--max-scripts", "--output", "--exclude"):
|
|
16
16
|
assert flag in run_text
|
|
17
|
+
assert "--changed-since" in run_text
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
def test_test_workflow_has_valid_yaml_and_python_indent():
|
|
@@ -6,7 +6,7 @@ import pytest
|
|
|
6
6
|
|
|
7
7
|
from opsscript_gate.cli import main
|
|
8
8
|
from opsscript_gate.config import CONFIG_NAME, init_project, load_config
|
|
9
|
-
from opsscript_gate.discovery import discover_scripts, is_shell_script
|
|
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
11
|
|
|
12
12
|
|
|
@@ -96,6 +96,40 @@ def test_discovery_stops_at_first_overflow(tmp_path, monkeypatch):
|
|
|
96
96
|
assert len(checked) == 3
|
|
97
97
|
|
|
98
98
|
|
|
99
|
+
def test_changed_discovery_uses_git_paths_and_filters_candidates(tmp_path):
|
|
100
|
+
(tmp_path / "install.sh").write_text("#!/bin/sh\necho ok\n")
|
|
101
|
+
(tmp_path / "README.md").write_text("docs\n")
|
|
102
|
+
completed = type("Completed", (), {"stdout": b"install.sh\0README.md\0", "stderr": b""})()
|
|
103
|
+
with patch("opsscript_gate.discovery.subprocess.run", return_value=completed) as run:
|
|
104
|
+
result = discover_changed_scripts(str(tmp_path), since="origin/main")
|
|
105
|
+
assert result == ["./install.sh"]
|
|
106
|
+
run.assert_called_once()
|
|
107
|
+
assert "origin/main...HEAD" in run.call_args.args[0]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@pytest.mark.parametrize("revision", ["", "--output=bad", "origin/main bad"])
|
|
111
|
+
def test_changed_discovery_rejects_invalid_revision(tmp_path, revision):
|
|
112
|
+
with pytest.raises(ValueError, match="changed-since"):
|
|
113
|
+
discover_changed_scripts(str(tmp_path), since=revision)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_changed_discovery_reports_empty_selection_as_success(project, monkeypatch, capsys):
|
|
117
|
+
monkeypatch.setattr("opsscript_gate.cli.discover_changed_scripts", lambda *args, **kwargs: [])
|
|
118
|
+
assert main(["run", "--changed-since", "origin/main"]) == 0
|
|
119
|
+
assert "No changed shell scripts" in capsys.readouterr().out
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_changed_discovery_preview_includes_revision(project, monkeypatch, capsys):
|
|
123
|
+
monkeypatch.setattr(
|
|
124
|
+
"opsscript_gate.cli.discover_changed_scripts",
|
|
125
|
+
lambda *args, **kwargs: ["./install.sh"],
|
|
126
|
+
)
|
|
127
|
+
assert main(["run", "--changed-since", "origin/main", "--dry-run", "--format", "json"]) == 0
|
|
128
|
+
plan = json.loads(capsys.readouterr().out)
|
|
129
|
+
assert plan["changed_since"] == "origin/main"
|
|
130
|
+
assert plan["scripts"] == ["./install.sh"]
|
|
131
|
+
|
|
132
|
+
|
|
99
133
|
@pytest.mark.parametrize("flags", [["--jobs", "0"], ["--timeout", "-1"],
|
|
100
134
|
["--pids-limit", "0"], ["--network", "host"], ["--matrix", ","],
|
|
101
135
|
["--matrix", "alpine", "--preset", "minimal"], ["--config", "missing.json"]])
|
|
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.5.1 → opsscript_gate-0.6.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
|