vsphere-triage 0.1.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.
- vsphere_triage-0.1.0/.github/workflows/ci.yml +59 -0
- vsphere_triage-0.1.0/.github/workflows/release.yml +234 -0
- vsphere_triage-0.1.0/.gitignore +8 -0
- vsphere_triage-0.1.0/AGENTS.md +56 -0
- vsphere_triage-0.1.0/LICENSE +21 -0
- vsphere_triage-0.1.0/PKG-INFO +364 -0
- vsphere_triage-0.1.0/README.md +337 -0
- vsphere_triage-0.1.0/docs/design.md +588 -0
- vsphere_triage-0.1.0/docs/orchestrator-integration.md +58 -0
- vsphere_triage-0.1.0/pyproject.toml +79 -0
- vsphere_triage-0.1.0/skills/vm-incident/SKILL.md +169 -0
- vsphere_triage-0.1.0/skills/vm-incident/core/causes.md +84 -0
- vsphere_triage-0.1.0/skills/vm-incident/core/correlation.md +71 -0
- vsphere_triage-0.1.0/skills/vm-incident/core/ladder.md +112 -0
- vsphere_triage-0.1.0/skills/vm-incident/core/verdict.md +74 -0
- vsphere_triage-0.1.0/skills/vm-incident/evals/evals.json +102 -0
- vsphere_triage-0.1.0/skills/vm-incident/examples/cpu-overcommit.md +69 -0
- vsphere_triage-0.1.0/skills/vm-incident/examples/storage-boot-storm.md +74 -0
- vsphere_triage-0.1.0/skills/vm-incident/glossary.md +28 -0
- vsphere_triage-0.1.0/tests/integration/conftest.py +192 -0
- vsphere_triage-0.1.0/tests/integration/scenario.py +800 -0
- vsphere_triage-0.1.0/tests/integration/test_fleet.py +552 -0
- vsphere_triage-0.1.0/tests/integration/test_signals.py +360 -0
- vsphere_triage-0.1.0/tests/test_logic.py +1379 -0
- vsphere_triage-0.1.0/vsphere_triage/__init__.py +1 -0
- vsphere_triage-0.1.0/vsphere_triage/__main__.py +5 -0
- vsphere_triage-0.1.0/vsphere_triage/ages.py +183 -0
- vsphere_triage-0.1.0/vsphere_triage/checks.py +337 -0
- vsphere_triage-0.1.0/vsphere_triage/cli.py +67 -0
- vsphere_triage-0.1.0/vsphere_triage/config.py +359 -0
- vsphere_triage-0.1.0/vsphere_triage/console.py +210 -0
- vsphere_triage-0.1.0/vsphere_triage/contention.py +408 -0
- vsphere_triage-0.1.0/vsphere_triage/diagnose.py +1013 -0
- vsphere_triage-0.1.0/vsphere_triage/formatting.py +58 -0
- vsphere_triage-0.1.0/vsphere_triage/guestexec.py +112 -0
- vsphere_triage-0.1.0/vsphere_triage/hardware.py +219 -0
- vsphere_triage-0.1.0/vsphere_triage/inventory.py +1184 -0
- vsphere_triage-0.1.0/vsphere_triage/readonly.py +111 -0
- vsphere_triage-0.1.0/vsphere_triage/resources.py +172 -0
- vsphere_triage-0.1.0/vsphere_triage/scan.py +669 -0
- vsphere_triage-0.1.0/vsphere_triage/server.py +1333 -0
- vsphere_triage-0.1.0/vsphere_triage/snapshots.py +174 -0
- vsphere_triage-0.1.0/vsphere_triage/storagehealth.py +180 -0
- vsphere_triage-0.1.0/vsphere_triage/timecheck.py +163 -0
- vsphere_triage-0.1.0/vsphere_triage/vcenter.py +920 -0
- vsphere_triage-0.1.0/vsphere_triage/vmlog.py +371 -0
- vsphere_triage-0.1.0/vsphere_triage/vrops.py +240 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
# The per-commit gate: lint, format and the unit suite, on Linux only. The
|
|
4
|
+
# vcsim integration suite pulls two Docker images and builds two simulated
|
|
5
|
+
# vCenters, so it is NOT run on every push — dispatch it by hand when you need
|
|
6
|
+
# it (Actions -> CI -> Run workflow -> integration = true), or before a release.
|
|
7
|
+
# release.yml reuses this file via workflow_call.
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
branches: [main]
|
|
11
|
+
pull_request:
|
|
12
|
+
workflow_call:
|
|
13
|
+
workflow_dispatch:
|
|
14
|
+
inputs:
|
|
15
|
+
integration:
|
|
16
|
+
description: "Also run the vcsim integration suite (Docker)"
|
|
17
|
+
type: boolean
|
|
18
|
+
default: false
|
|
19
|
+
|
|
20
|
+
# One cheap runner per ref. Superseded pull-request runs are cancelled; pushes
|
|
21
|
+
# to main are left to finish.
|
|
22
|
+
concurrency:
|
|
23
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
24
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
25
|
+
|
|
26
|
+
permissions:
|
|
27
|
+
contents: read
|
|
28
|
+
|
|
29
|
+
jobs:
|
|
30
|
+
check:
|
|
31
|
+
name: Lint & unit tests
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
timeout-minutes: 10
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v4
|
|
36
|
+
- uses: astral-sh/setup-uv@v5
|
|
37
|
+
with:
|
|
38
|
+
enable-cache: true
|
|
39
|
+
- name: Lint
|
|
40
|
+
run: uv run --extra dev ruff check .
|
|
41
|
+
- name: Format
|
|
42
|
+
run: uv run --extra dev ruff format --check .
|
|
43
|
+
- name: Unit tests
|
|
44
|
+
run: uv run --extra dev python -m pytest -m "not integration" -q
|
|
45
|
+
|
|
46
|
+
integration:
|
|
47
|
+
name: vcsim integration (Docker)
|
|
48
|
+
# Expensive: pulls vmware/vcsim + vmware/govc and builds two simulated
|
|
49
|
+
# vCenters. Manual only, so it never runs on an ordinary push.
|
|
50
|
+
if: ${{ github.event_name == 'workflow_dispatch' && inputs.integration }}
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
timeout-minutes: 25
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/checkout@v4
|
|
55
|
+
- uses: astral-sh/setup-uv@v5
|
|
56
|
+
with:
|
|
57
|
+
enable-cache: true
|
|
58
|
+
- name: Full suite (unit + vcsim integration)
|
|
59
|
+
run: uv run --extra dev python -m pytest -q
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Bump the version in pyproject.toml, merge to main, and the release runs
|
|
4
|
+
# itself: a push to main whose version has no vX.Y.Z tag yet builds the sdist +
|
|
5
|
+
# wheel, verifies the wheel installs and imports on Linux, publishes to PyPI,
|
|
6
|
+
# then tags the commit vX.Y.Z and makes the GitHub release. A merge that does
|
|
7
|
+
# not change the version stops at the gate. Tagging by hand still works:
|
|
8
|
+
#
|
|
9
|
+
# git tag v0.2.0 && git push origin v0.2.0
|
|
10
|
+
#
|
|
11
|
+
# Publishing uses PyPI Trusted Publishing (OIDC) — there is no token in the
|
|
12
|
+
# repo. Configure the publisher once at pypi.org/manage/account/publishing
|
|
13
|
+
# (project: vsphere-triage, workflow: release.yml, environment: pypi), and the
|
|
14
|
+
# same for test.pypi.org if you use the testpypi dispatch.
|
|
15
|
+
#
|
|
16
|
+
# Linux only, on purpose: this is a pure-Python package, so one platform
|
|
17
|
+
# verifies the artifact and keeps the Actions bill small.
|
|
18
|
+
on:
|
|
19
|
+
push:
|
|
20
|
+
branches: [main]
|
|
21
|
+
tags: ["v*"]
|
|
22
|
+
workflow_dispatch:
|
|
23
|
+
inputs:
|
|
24
|
+
repository:
|
|
25
|
+
description: "Where to publish from the current branch"
|
|
26
|
+
type: choice
|
|
27
|
+
options: [testpypi, pypi]
|
|
28
|
+
default: testpypi
|
|
29
|
+
|
|
30
|
+
permissions:
|
|
31
|
+
contents: read
|
|
32
|
+
|
|
33
|
+
# Two merges in quick succession release one after the other, never at once.
|
|
34
|
+
concurrency:
|
|
35
|
+
group: release
|
|
36
|
+
cancel-in-progress: false
|
|
37
|
+
|
|
38
|
+
jobs:
|
|
39
|
+
gate:
|
|
40
|
+
name: Anything to release?
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
timeout-minutes: 5
|
|
43
|
+
outputs:
|
|
44
|
+
release: ${{ steps.gate.outputs.release }}
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/checkout@v4
|
|
47
|
+
- name: Is pyproject.toml's version tagged yet?
|
|
48
|
+
id: gate
|
|
49
|
+
shell: bash
|
|
50
|
+
run: |
|
|
51
|
+
set -euo pipefail
|
|
52
|
+
# A tag push or a manual dispatch is a release by definition.
|
|
53
|
+
if [ "$GITHUB_EVENT_NAME" != push ] || [ "$GITHUB_REF_TYPE" = tag ]; then
|
|
54
|
+
echo "release=true" >> "$GITHUB_OUTPUT"; exit 0
|
|
55
|
+
fi
|
|
56
|
+
version=$(python3 -c "import pathlib,tomllib;print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")
|
|
57
|
+
if git ls-remote --exit-code origin "refs/tags/v$version" >/dev/null; then
|
|
58
|
+
echo "v$version is already released and this merge does not bump the version: nothing to do."
|
|
59
|
+
echo "release=false" >> "$GITHUB_OUTPUT"
|
|
60
|
+
else
|
|
61
|
+
echo "v$version is not tagged yet: releasing it."
|
|
62
|
+
echo "release=true" >> "$GITHUB_OUTPUT"
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
build:
|
|
66
|
+
name: Build
|
|
67
|
+
needs: gate
|
|
68
|
+
if: needs.gate.outputs.release == 'true'
|
|
69
|
+
runs-on: ubuntu-latest
|
|
70
|
+
timeout-minutes: 10
|
|
71
|
+
outputs:
|
|
72
|
+
version: ${{ steps.version.outputs.version }}
|
|
73
|
+
prerelease: ${{ steps.version.outputs.prerelease }}
|
|
74
|
+
steps:
|
|
75
|
+
- uses: actions/checkout@v4
|
|
76
|
+
- uses: actions/setup-python@v5
|
|
77
|
+
with:
|
|
78
|
+
python-version: "3.12"
|
|
79
|
+
- name: Read the version, and check a tag agrees with it
|
|
80
|
+
id: version
|
|
81
|
+
shell: bash
|
|
82
|
+
run: |
|
|
83
|
+
set -euo pipefail
|
|
84
|
+
version=$(python -c "import pathlib,tomllib;print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")
|
|
85
|
+
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
86
|
+
# A tag vX.Y.Z that builds a different version uploads the wrong thing
|
|
87
|
+
# under the right name, and PyPI will not let you take it back.
|
|
88
|
+
if [ "${GITHUB_REF_TYPE:-}" = "tag" ]; then
|
|
89
|
+
tagged="${GITHUB_REF_NAME#v}"
|
|
90
|
+
if [ "$tagged" != "$version" ]; then
|
|
91
|
+
echo "::error::tag $GITHUB_REF_NAME does not match pyproject version $version"; exit 1
|
|
92
|
+
fi
|
|
93
|
+
fi
|
|
94
|
+
# A tag for this version that already exists must be on this commit —
|
|
95
|
+
# checked here, before the irreversible publish, not only after it.
|
|
96
|
+
at=$(git ls-remote origin "refs/tags/v$version" "refs/tags/v$version^{}" | awk 'END {print $1}')
|
|
97
|
+
if [ -n "$at" ] && [ "$at" != "$GITHUB_SHA" ]; then
|
|
98
|
+
echo "::error::v$version is already on $at, not the commit this run would publish ($GITHUB_SHA)"; exit 1
|
|
99
|
+
fi
|
|
100
|
+
# PEP 440 prereleases: 1.2.0rc1, no separator.
|
|
101
|
+
if printf '%s' "$version" | grep -Eq '(a|b|rc|dev)[0-9]*$'; then
|
|
102
|
+
echo "prerelease=true" >> "$GITHUB_OUTPUT"
|
|
103
|
+
else
|
|
104
|
+
echo "prerelease=false" >> "$GITHUB_OUTPUT"
|
|
105
|
+
fi
|
|
106
|
+
- name: Build the sdist and the wheel
|
|
107
|
+
run: |
|
|
108
|
+
python -m pip install --upgrade pip build twine
|
|
109
|
+
python -m build
|
|
110
|
+
twine check --strict dist/*
|
|
111
|
+
- run: ls -l dist/
|
|
112
|
+
- uses: actions/upload-artifact@v4
|
|
113
|
+
with:
|
|
114
|
+
name: distribution
|
|
115
|
+
path: dist/
|
|
116
|
+
if-no-files-found: error
|
|
117
|
+
|
|
118
|
+
verify:
|
|
119
|
+
# Verify the built artifact, not the checkout: install the wheel into a
|
|
120
|
+
# clean venv, import the package, construct the server, and run the console
|
|
121
|
+
# script — so a packaging mistake (a missing module, a broken entry point)
|
|
122
|
+
# is caught before publish, not after.
|
|
123
|
+
name: Verify the wheel
|
|
124
|
+
needs: build
|
|
125
|
+
runs-on: ubuntu-latest
|
|
126
|
+
timeout-minutes: 10
|
|
127
|
+
steps:
|
|
128
|
+
- uses: actions/setup-python@v5
|
|
129
|
+
with:
|
|
130
|
+
python-version: "3.12"
|
|
131
|
+
- uses: actions/download-artifact@v4
|
|
132
|
+
with:
|
|
133
|
+
name: distribution
|
|
134
|
+
path: dist
|
|
135
|
+
- name: Install the built wheel and smoke-test it
|
|
136
|
+
shell: bash
|
|
137
|
+
run: |
|
|
138
|
+
set -euo pipefail
|
|
139
|
+
python -m venv /tmp/venv
|
|
140
|
+
/tmp/venv/bin/pip install --upgrade pip
|
|
141
|
+
/tmp/venv/bin/pip install "$(ls dist/*.whl)"
|
|
142
|
+
/tmp/venv/bin/python - <<'CHECK'
|
|
143
|
+
import vsphere_triage
|
|
144
|
+
from vsphere_triage.config import Settings
|
|
145
|
+
from vsphere_triage.server import build_server
|
|
146
|
+
located = vsphere_triage.__file__.replace("\\", "/")
|
|
147
|
+
assert "site-packages" in located, f"tested the checkout, not the wheel: {located}"
|
|
148
|
+
settings = Settings.from_environment({
|
|
149
|
+
"VSPHERE_VCENTERS": '[{"name": "vc", "host": "h"}]',
|
|
150
|
+
"VSPHERE_USERNAME": "u",
|
|
151
|
+
"VSPHERE_PASSWORD": "p",
|
|
152
|
+
})
|
|
153
|
+
build_server(settings) # the whole tool surface wires up
|
|
154
|
+
print("imported from:", located)
|
|
155
|
+
CHECK
|
|
156
|
+
/tmp/venv/bin/vsphere-triage --help > /dev/null
|
|
157
|
+
echo "console script runs"
|
|
158
|
+
|
|
159
|
+
publish:
|
|
160
|
+
name: Publish to ${{ (github.event_name == 'push' && 'pypi') || inputs.repository }}
|
|
161
|
+
needs: [build, verify]
|
|
162
|
+
runs-on: ubuntu-latest
|
|
163
|
+
timeout-minutes: 10
|
|
164
|
+
# Trusted publishing: PyPI verifies this workflow's OIDC identity, so there
|
|
165
|
+
# is no long-lived token in the repo. A manual approval gate, if wanted, is
|
|
166
|
+
# a property of the environment (Settings -> Environments -> pypi ->
|
|
167
|
+
# Required reviewers), not of this file, so it cannot be removed by editing
|
|
168
|
+
# a branch.
|
|
169
|
+
environment:
|
|
170
|
+
name: ${{ (github.event_name == 'push' && 'pypi') || inputs.repository }}
|
|
171
|
+
url: ${{ ((github.event_name == 'push' || inputs.repository == 'pypi') && 'https://pypi.org/p/vsphere-triage') || 'https://test.pypi.org/p/vsphere-triage' }}
|
|
172
|
+
permissions:
|
|
173
|
+
id-token: write
|
|
174
|
+
steps:
|
|
175
|
+
- uses: actions/download-artifact@v4
|
|
176
|
+
with:
|
|
177
|
+
name: distribution
|
|
178
|
+
path: dist
|
|
179
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
180
|
+
with:
|
|
181
|
+
repository-url: ${{ ((github.event_name == 'push' || inputs.repository == 'pypi') && 'https://upload.pypi.org/legacy/') || 'https://test.pypi.org/legacy/' }}
|
|
182
|
+
print-hash: true
|
|
183
|
+
# So a run that uploaded and then failed later can be re-run: PyPI
|
|
184
|
+
# never replaces a file it already has, whatever this is set to.
|
|
185
|
+
skip-existing: true
|
|
186
|
+
|
|
187
|
+
release:
|
|
188
|
+
# Last, and only once PyPI has accepted the upload. The runner's token
|
|
189
|
+
# writes the tag, so a release need not be tagged up front.
|
|
190
|
+
name: Tag and release
|
|
191
|
+
needs: [build, publish]
|
|
192
|
+
if: github.event_name == 'push' || inputs.repository == 'pypi'
|
|
193
|
+
runs-on: ubuntu-latest
|
|
194
|
+
timeout-minutes: 10
|
|
195
|
+
permissions:
|
|
196
|
+
contents: write
|
|
197
|
+
steps:
|
|
198
|
+
- uses: actions/checkout@v4
|
|
199
|
+
- uses: actions/download-artifact@v4
|
|
200
|
+
with:
|
|
201
|
+
name: distribution
|
|
202
|
+
path: dist
|
|
203
|
+
- name: Make sure the published commit is tagged
|
|
204
|
+
shell: bash
|
|
205
|
+
run: |
|
|
206
|
+
set -euo pipefail
|
|
207
|
+
tag="v${{ needs.build.outputs.version }}"
|
|
208
|
+
at=$(git ls-remote origin "refs/tags/$tag" "refs/tags/$tag^{}" | awk 'END {print $1}')
|
|
209
|
+
if [ -n "$at" ]; then
|
|
210
|
+
if [ "$at" != "$GITHUB_SHA" ]; then
|
|
211
|
+
echo "::error::$tag is already on $at, not the commit this run published ($GITHUB_SHA)"; exit 1
|
|
212
|
+
fi
|
|
213
|
+
echo "$tag already exists here (tag-push path)"
|
|
214
|
+
else
|
|
215
|
+
git config user.name "github-actions[bot]"
|
|
216
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
217
|
+
git tag -a "$tag" -m "vsphere-triage ${{ needs.build.outputs.version }}"
|
|
218
|
+
git push origin "$tag"
|
|
219
|
+
fi
|
|
220
|
+
echo "TAG=$tag" >> "$GITHUB_ENV"
|
|
221
|
+
- name: Create the GitHub release
|
|
222
|
+
env:
|
|
223
|
+
GH_TOKEN: ${{ github.token }}
|
|
224
|
+
run: |
|
|
225
|
+
set -euo pipefail
|
|
226
|
+
if gh release view "$TAG" >/dev/null 2>&1; then
|
|
227
|
+
gh release upload "$TAG" dist/* --clobber
|
|
228
|
+
else
|
|
229
|
+
gh release create "$TAG" dist/* \
|
|
230
|
+
--title "$TAG" \
|
|
231
|
+
--generate-notes \
|
|
232
|
+
--verify-tag \
|
|
233
|
+
${{ needs.build.outputs.prerelease == 'true' && '--prerelease' || '' }}
|
|
234
|
+
fi
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# CI policy for coding agents
|
|
2
|
+
|
|
3
|
+
CI cost matters here. The workflows are deliberately **Linux-only and
|
|
4
|
+
single-job**, because this is a pure-Python package that needs no
|
|
5
|
+
cross-platform matrix. Keep them that way unless a real portability bug forces
|
|
6
|
+
otherwise.
|
|
7
|
+
|
|
8
|
+
## The per-commit gate
|
|
9
|
+
|
|
10
|
+
`.github/workflows/ci.yml` runs on every push and pull request: one job that
|
|
11
|
+
lints (`ruff check`), checks formatting (`ruff format --check`), and runs the
|
|
12
|
+
**unit** suite (`pytest -m "not integration"`). That is the whole required
|
|
13
|
+
gate. Do not add an OS matrix, extra Python versions, or more jobs to it to
|
|
14
|
+
chase a single failure — a change that makes CI slower or costlier needs a
|
|
15
|
+
reason beyond convenience.
|
|
16
|
+
|
|
17
|
+
## The integration suite is not free, and not per-commit
|
|
18
|
+
|
|
19
|
+
`tests/integration/` builds **two simulated vCenters** with `vmware/vcsim` and
|
|
20
|
+
`vmware/govc` (two image pulls, ~90 s of work). It is marked with the
|
|
21
|
+
`integration` pytest marker and is **never** on the per-commit path. Run it:
|
|
22
|
+
|
|
23
|
+
- locally: `uv run --extra dev python -m pytest` (needs a running Docker), or
|
|
24
|
+
- on CI by hand: Actions → CI → Run workflow → `integration = true`.
|
|
25
|
+
|
|
26
|
+
Dispatch it before a release, or when you change anything that touches the
|
|
27
|
+
vCenter session layer, the read-only guard, or the scenario fixtures. Do not
|
|
28
|
+
wire it into `ci.yml`'s automatic triggers.
|
|
29
|
+
|
|
30
|
+
## Local iteration
|
|
31
|
+
|
|
32
|
+
- `uv run --extra dev python -m pytest -m "not integration" -q` — the fast gate
|
|
33
|
+
(no Docker, ~1 s).
|
|
34
|
+
- `uv run --extra dev python -m pytest -q` — everything, Docker required.
|
|
35
|
+
- `pytest --lf` / `pytest --ff` — re-run last failures first while iterating;
|
|
36
|
+
don't delete `.pytest_cache` mid-investigation.
|
|
37
|
+
|
|
38
|
+
## Releasing
|
|
39
|
+
|
|
40
|
+
`.github/workflows/release.yml` publishes to PyPI via **Trusted Publishing**
|
|
41
|
+
(OIDC) — there is **no token** in the repo, and none should be added. Release
|
|
42
|
+
is a version bump: raise `version` in `pyproject.toml`, merge to `main`, and the
|
|
43
|
+
workflow builds, verifies the wheel, publishes, tags, and makes the GitHub
|
|
44
|
+
release. A merge that does not change the version stops at the gate. Never
|
|
45
|
+
commit a PyPI token, and never weaken the version/tag consistency checks in
|
|
46
|
+
`build` — they are the guard against publishing the wrong artifact under a
|
|
47
|
+
version PyPI will not let you reclaim.
|
|
48
|
+
|
|
49
|
+
## The read-only guarantee
|
|
50
|
+
|
|
51
|
+
Every tool reads only, with one marked exception (`run_in_guest`). A test sweeps
|
|
52
|
+
every read-only tool and asserts the vSphere methods sent are a subset of
|
|
53
|
+
`readonly.ALLOWED_METHODS`; another asserts guest exec never dirties that
|
|
54
|
+
surface. If you add a tool or a vSphere call, keep both tests passing — do not
|
|
55
|
+
add a method to the allowlist to silence the sweep without understanding why it
|
|
56
|
+
is being sent.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Heknon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|