harbor-aiops 0.2.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.
- harbor_aiops-0.2.0/.claude-plugin/plugin.json +22 -0
- harbor_aiops-0.2.0/.dockerignore +7 -0
- harbor_aiops-0.2.0/.github/workflows/ci.yml +28 -0
- harbor_aiops-0.2.0/.github/workflows/mcp-publish.yml +57 -0
- harbor_aiops-0.2.0/.github/workflows/publish.yml +28 -0
- harbor_aiops-0.2.0/.gitignore +23 -0
- harbor_aiops-0.2.0/.mcp.json +13 -0
- harbor_aiops-0.2.0/CHANGELOG.md +13 -0
- harbor_aiops-0.2.0/Dockerfile +6 -0
- harbor_aiops-0.2.0/LICENSE +21 -0
- harbor_aiops-0.2.0/PKG-INFO +60 -0
- harbor_aiops-0.2.0/README.md +44 -0
- harbor_aiops-0.2.0/SECURITY.md +7 -0
- harbor_aiops-0.2.0/docs/VERIFICATION.md +15 -0
- harbor_aiops-0.2.0/glama.json +6 -0
- harbor_aiops-0.2.0/harbor_aiops/__init__.py +8 -0
- harbor_aiops-0.2.0/harbor_aiops/cli/__init__.py +9 -0
- harbor_aiops-0.2.0/harbor_aiops/cli/_common.py +122 -0
- harbor_aiops-0.2.0/harbor_aiops/cli/_root.py +446 -0
- harbor_aiops-0.2.0/harbor_aiops/cli/secret.py +106 -0
- harbor_aiops-0.2.0/harbor_aiops/cli/undo.py +66 -0
- harbor_aiops-0.2.0/harbor_aiops/config.py +95 -0
- harbor_aiops-0.2.0/harbor_aiops/connection.py +188 -0
- harbor_aiops-0.2.0/harbor_aiops/governance/__init__.py +50 -0
- harbor_aiops-0.2.0/harbor_aiops/governance/audit.py +521 -0
- harbor_aiops-0.2.0/harbor_aiops/governance/budget.py +222 -0
- harbor_aiops-0.2.0/harbor_aiops/governance/decorators.py +574 -0
- harbor_aiops-0.2.0/harbor_aiops/governance/outcome.py +104 -0
- harbor_aiops-0.2.0/harbor_aiops/governance/paths.py +23 -0
- harbor_aiops-0.2.0/harbor_aiops/governance/patterns.py +378 -0
- harbor_aiops-0.2.0/harbor_aiops/governance/policy.py +67 -0
- harbor_aiops-0.2.0/harbor_aiops/governance/sanitize.py +79 -0
- harbor_aiops-0.2.0/harbor_aiops/governance/undo.py +257 -0
- harbor_aiops-0.2.0/harbor_aiops/mcp_server/__init__.py +1 -0
- harbor_aiops-0.2.0/harbor_aiops/mcp_server/_annotations.py +66 -0
- harbor_aiops-0.2.0/harbor_aiops/mcp_server/_shared.py +87 -0
- harbor_aiops-0.2.0/harbor_aiops/mcp_server/server.py +14 -0
- harbor_aiops-0.2.0/harbor_aiops/mcp_server/tools/__init__.py +1 -0
- harbor_aiops-0.2.0/harbor_aiops/mcp_server/tools/registry.py +313 -0
- harbor_aiops-0.2.0/harbor_aiops/mcp_server/tools/undo.py +204 -0
- harbor_aiops-0.2.0/harbor_aiops/mcp_server/tools/writes.py +46 -0
- harbor_aiops-0.2.0/harbor_aiops/ops/__init__.py +1 -0
- harbor_aiops-0.2.0/harbor_aiops/ops/registry.py +258 -0
- harbor_aiops-0.2.0/harbor_aiops/ops/writes.py +117 -0
- harbor_aiops-0.2.0/harbor_aiops/secretstore.py +306 -0
- harbor_aiops-0.2.0/openclaw.plugin.json +24 -0
- harbor_aiops-0.2.0/package.json +20 -0
- harbor_aiops-0.2.0/pyproject.toml +61 -0
- harbor_aiops-0.2.0/server.json +21 -0
- harbor_aiops-0.2.0/skills/harbor-aiops/SKILL.md +24 -0
- harbor_aiops-0.2.0/skills/harbor-aiops/references/agent-guardrails.md +13 -0
- harbor_aiops-0.2.0/skills/harbor-aiops/references/capabilities.md +5 -0
- harbor_aiops-0.2.0/skills/harbor-aiops/references/cli-reference.md +13 -0
- harbor_aiops-0.2.0/skills/harbor-aiops/references/setup-guide.md +7 -0
- harbor_aiops-0.2.0/tests/test_audit_guardrails.py +209 -0
- harbor_aiops-0.2.0/tests/test_cli_audited.py +48 -0
- harbor_aiops-0.2.0/tests/test_cli_governed_exit.py +18 -0
- harbor_aiops-0.2.0/tests/test_gov_audit.py +292 -0
- harbor_aiops-0.2.0/tests/test_gov_decorators.py +433 -0
- harbor_aiops-0.2.0/tests/test_gov_patterns.py +417 -0
- harbor_aiops-0.2.0/tests/test_gov_policy.py +56 -0
- harbor_aiops-0.2.0/tests/test_governance_persistence.py +331 -0
- harbor_aiops-0.2.0/tests/test_package_layout.py +62 -0
- harbor_aiops-0.2.0/tests/test_registry.py +361 -0
- harbor_aiops-0.2.0/tests/test_secretstore.py +99 -0
- harbor_aiops-0.2.0/tests/test_smoke.py +28 -0
- harbor_aiops-0.2.0/tests/test_tool_annotations.py +139 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
|
|
3
|
+
"name": "harbor-aiops",
|
|
4
|
+
"displayName": "Harbor AIops",
|
|
5
|
+
"version": "0.2.0",
|
|
6
|
+
"description": "Governed Harbor registry operations — 39 MCP tools with audit.",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "Wei Zhou",
|
|
9
|
+
"email": "zhouwei008@gmail.com",
|
|
10
|
+
"url": "https://github.com/AIops-tools"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/AIops-tools/Harbor-AIops",
|
|
13
|
+
"repository": "https://github.com/AIops-tools/Harbor-AIops",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"harbor",
|
|
17
|
+
"container-registry",
|
|
18
|
+
"oci",
|
|
19
|
+
"mcp",
|
|
20
|
+
"aiops"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [bootstrap, main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
quality:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: astral-sh/setup-uv@v5
|
|
17
|
+
- name: Install Python and dependencies
|
|
18
|
+
run: |
|
|
19
|
+
uv python install 3.11
|
|
20
|
+
uv sync --dev --python 3.11
|
|
21
|
+
- name: Tests
|
|
22
|
+
run: uv run pytest -q
|
|
23
|
+
- name: Lint
|
|
24
|
+
run: uv run ruff check .
|
|
25
|
+
- name: Security scan
|
|
26
|
+
run: uv run bandit -r harbor_aiops -ll -q
|
|
27
|
+
- name: Build
|
|
28
|
+
run: uv build
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: mcp-publish
|
|
2
|
+
|
|
3
|
+
# Registry publication follows the Harbor wheel's first appearance on PyPI.
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
release:
|
|
8
|
+
types: [published]
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
id-token: write
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
publish-mcp:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- name: Wait for PyPI
|
|
20
|
+
# The release event also triggers the PyPI publish workflow; the MCP
|
|
21
|
+
# registry validates that the package version exists on PyPI, so poll
|
|
22
|
+
# until it has propagated (every 15s, up to 10 minutes).
|
|
23
|
+
run: |
|
|
24
|
+
python3 - <<'EOF'
|
|
25
|
+
import json
|
|
26
|
+
import sys
|
|
27
|
+
import time
|
|
28
|
+
import urllib.error
|
|
29
|
+
import urllib.request
|
|
30
|
+
|
|
31
|
+
with open("server.json", encoding="utf-8") as f:
|
|
32
|
+
package = json.load(f)["packages"][0]
|
|
33
|
+
name, version = package["identifier"], package["version"]
|
|
34
|
+
url = f"https://pypi.org/pypi/{name}/{version}/json"
|
|
35
|
+
deadline = time.monotonic() + 600
|
|
36
|
+
while True:
|
|
37
|
+
try:
|
|
38
|
+
with urllib.request.urlopen(url, timeout=10):
|
|
39
|
+
print(f"{name}=={version} is available on PyPI.")
|
|
40
|
+
sys.exit(0)
|
|
41
|
+
except (urllib.error.URLError, OSError) as exc:
|
|
42
|
+
print(f"{name}=={version} not on PyPI yet ({exc}); "
|
|
43
|
+
"retrying in 15s...")
|
|
44
|
+
if time.monotonic() >= deadline:
|
|
45
|
+
sys.exit(f"Timed out after 10 minutes waiting for "
|
|
46
|
+
f"{name}=={version} to appear on PyPI. "
|
|
47
|
+
"Check the PyPI publish workflow, then re-run "
|
|
48
|
+
"this workflow via workflow_dispatch.")
|
|
49
|
+
time.sleep(15)
|
|
50
|
+
EOF
|
|
51
|
+
- name: Install mcp-publisher
|
|
52
|
+
run: |
|
|
53
|
+
curl -sL "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
|
|
54
|
+
- name: Login to MCP Registry (GitHub OIDC)
|
|
55
|
+
run: ./mcp-publisher login github-oidc
|
|
56
|
+
- name: Publish server.json
|
|
57
|
+
run: ./mcp-publisher publish
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# First public package release uses the Harbor-specific pending PyPI publisher.
|
|
4
|
+
|
|
5
|
+
# Trusted Publishing (OIDC) — publishes from GitHub's runners with no API token,
|
|
6
|
+
# sidestepping the local-IP / account new-project rate limit. Configure a matching
|
|
7
|
+
# "trusted publisher" for this package on PyPI (see the repo release notes).
|
|
8
|
+
on:
|
|
9
|
+
release:
|
|
10
|
+
types: [published]
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
publish:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
permissions:
|
|
20
|
+
id-token: write # required for PyPI Trusted Publishing (OIDC)
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- name: Set up uv
|
|
24
|
+
uses: astral-sh/setup-uv@v5
|
|
25
|
+
- name: Build sdist + wheel
|
|
26
|
+
run: uv build
|
|
27
|
+
- name: Publish to PyPI (Trusted Publishing)
|
|
28
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.pytest_cache/
|
|
6
|
+
.ruff_cache/
|
|
7
|
+
|
|
8
|
+
# Virtual envs / build
|
|
9
|
+
.venv/
|
|
10
|
+
dist/
|
|
11
|
+
build/
|
|
12
|
+
|
|
13
|
+
# uv
|
|
14
|
+
uv.lock
|
|
15
|
+
|
|
16
|
+
# Local config / secrets (never commit)
|
|
17
|
+
*.env
|
|
18
|
+
.env
|
|
19
|
+
config.yaml
|
|
20
|
+
|
|
21
|
+
# OS
|
|
22
|
+
.DS_Store
|
|
23
|
+
.coverage
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## v0.2.0 — 2026-11-05
|
|
4
|
+
|
|
5
|
+
- Expand to 39 governed MCP/CLI tools for project, repository, artifact, tag, vulnerability, quota, GC, replication and retention operations.
|
|
6
|
+
- Add bounded job logs, measured pagination truncation and explicit failed probes.
|
|
7
|
+
- Add guarded artifact scan, tag creation and exact mutable-tag deletion; preserve unknown outcomes on asynchronous work or lost responses.
|
|
8
|
+
- Limit Docker build context and image copies to package source and packaging files.
|
|
9
|
+
- Add GitHub CI for tests, lint, security scan and build.
|
|
10
|
+
|
|
11
|
+
## 0.1.0
|
|
12
|
+
|
|
13
|
+
- Initial local development version; not published.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wei <zhouwei008@gmail.com>
|
|
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.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: harbor-aiops
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Governed Harbor registry diagnosis for health, storage, GC, replication and retention.
|
|
5
|
+
Author-email: Wei Zhou <zhouwei008@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: cryptography>=42.0
|
|
10
|
+
Requires-Dist: httpx<1.0,>=0.27
|
|
11
|
+
Requires-Dist: mcp[cli]<3.0,>=2.0
|
|
12
|
+
Requires-Dist: pyyaml<7.0,>=6.0
|
|
13
|
+
Requires-Dist: rich<16.0,>=13.0
|
|
14
|
+
Requires-Dist: typer<1.0,>=0.12
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
<!-- mcp-name: io.github.AIops-tools/harbor-aiops -->
|
|
18
|
+
# Harbor AIops
|
|
19
|
+
|
|
20
|
+
Governed Harbor container-registry operations with 39 MCP tools and corresponding CLI commands. Every call is audited and budgeted by the bundled governance harness. Artifact scan, tag creation and high-risk tag deletion have dry-run previews; uncertain results are reported as `outcomeUnknown`.
|
|
21
|
+
|
|
22
|
+
## What it checks
|
|
23
|
+
|
|
24
|
+
- Component health and system volume use.
|
|
25
|
+
- Projects, repository inventory, artifacts, tags, vulnerability reports, quotas and project audit logs.
|
|
26
|
+
- Registry statistics, project members, tag immutability rules, labels and robot metadata (secrets redacted).
|
|
27
|
+
- Latest registry-wide and scheduled scan metrics, plus bounded artifact scan report logs.
|
|
28
|
+
- Guarded deletion of one proven, mutable tag with an absence post-check.
|
|
29
|
+
- Garbage collection execution history.
|
|
30
|
+
- GC execution details and bounded plain-text logs.
|
|
31
|
+
- Replication policies, executions, tasks and bounded task logs.
|
|
32
|
+
- Retention policy, execution, tasks and bounded task logs by actual IDs.
|
|
33
|
+
- `harbor_diagnose` combines health, volume, GC and replication observations. Failed probes appear as errors with `data: null`.
|
|
34
|
+
|
|
35
|
+
Lists have `items`, `returned`, `limit`, and measured `truncated` fields. They are capped at 500 records per call.
|
|
36
|
+
|
|
37
|
+
## Setup from source
|
|
38
|
+
|
|
39
|
+
Requires Python 3.11+, `uv`, and a Harbor instance. Install locally with `uv sync --dev`. Run `uv run harbor-aiops init` to configure an HTTPS target and encrypt its password, or create `~/.harbor-aiops/config.yaml` with a `targets` list:
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
targets:
|
|
43
|
+
- name: primary
|
|
44
|
+
host: harbor.example.com
|
|
45
|
+
username: robot$ops
|
|
46
|
+
scheme: https
|
|
47
|
+
port: 443
|
|
48
|
+
verify_ssl: true
|
|
49
|
+
timeout: 30
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Run `uv run harbor-aiops secret set primary`, then `uv run harbor-aiops doctor` and `uv run harbor-aiops diagnose`. The MCP entry point is `uv run harbor-aiops-mcp`. Set `HARBOR_AIOPS_MASTER_PASSWORD` for noninteractive secret-store access. TLS verification is enabled by default; `ca_file` accepts a custom CA PEM.
|
|
53
|
+
|
|
54
|
+
## Scope and verification
|
|
55
|
+
|
|
56
|
+
The `scan-artifact` command starts an asynchronous scan and reports an unknown final outcome until its status is checked. `create-tag` proves the tag was absent and checks that it appeared after the write. `delete-tag` requires an exact mutable tag on the specified digest, previews the target and confirms absence after deletion. Neither tag write mints an undo token because immutability and later artifact changes can make the inverse unsafe. Retention execution, replication restart, GC start/stop, artifact deletion and policy changes remain outside this release until their actual outcomes and recovery paths can be verified on disposable Harbor. See [verification](docs/VERIFICATION.md). Missing operations and live-environment feedback are welcome as issues or PRs.
|
|
57
|
+
|
|
58
|
+
## Development
|
|
59
|
+
|
|
60
|
+
`uv run pytest -q`, `uv run ruff check .`, and `uv run bandit -r harbor_aiops -ll` are local checks. Root handbook gates must pass before release.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<!-- mcp-name: io.github.AIops-tools/harbor-aiops -->
|
|
2
|
+
# Harbor AIops
|
|
3
|
+
|
|
4
|
+
Governed Harbor container-registry operations with 39 MCP tools and corresponding CLI commands. Every call is audited and budgeted by the bundled governance harness. Artifact scan, tag creation and high-risk tag deletion have dry-run previews; uncertain results are reported as `outcomeUnknown`.
|
|
5
|
+
|
|
6
|
+
## What it checks
|
|
7
|
+
|
|
8
|
+
- Component health and system volume use.
|
|
9
|
+
- Projects, repository inventory, artifacts, tags, vulnerability reports, quotas and project audit logs.
|
|
10
|
+
- Registry statistics, project members, tag immutability rules, labels and robot metadata (secrets redacted).
|
|
11
|
+
- Latest registry-wide and scheduled scan metrics, plus bounded artifact scan report logs.
|
|
12
|
+
- Guarded deletion of one proven, mutable tag with an absence post-check.
|
|
13
|
+
- Garbage collection execution history.
|
|
14
|
+
- GC execution details and bounded plain-text logs.
|
|
15
|
+
- Replication policies, executions, tasks and bounded task logs.
|
|
16
|
+
- Retention policy, execution, tasks and bounded task logs by actual IDs.
|
|
17
|
+
- `harbor_diagnose` combines health, volume, GC and replication observations. Failed probes appear as errors with `data: null`.
|
|
18
|
+
|
|
19
|
+
Lists have `items`, `returned`, `limit`, and measured `truncated` fields. They are capped at 500 records per call.
|
|
20
|
+
|
|
21
|
+
## Setup from source
|
|
22
|
+
|
|
23
|
+
Requires Python 3.11+, `uv`, and a Harbor instance. Install locally with `uv sync --dev`. Run `uv run harbor-aiops init` to configure an HTTPS target and encrypt its password, or create `~/.harbor-aiops/config.yaml` with a `targets` list:
|
|
24
|
+
|
|
25
|
+
```yaml
|
|
26
|
+
targets:
|
|
27
|
+
- name: primary
|
|
28
|
+
host: harbor.example.com
|
|
29
|
+
username: robot$ops
|
|
30
|
+
scheme: https
|
|
31
|
+
port: 443
|
|
32
|
+
verify_ssl: true
|
|
33
|
+
timeout: 30
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Run `uv run harbor-aiops secret set primary`, then `uv run harbor-aiops doctor` and `uv run harbor-aiops diagnose`. The MCP entry point is `uv run harbor-aiops-mcp`. Set `HARBOR_AIOPS_MASTER_PASSWORD` for noninteractive secret-store access. TLS verification is enabled by default; `ca_file` accepts a custom CA PEM.
|
|
37
|
+
|
|
38
|
+
## Scope and verification
|
|
39
|
+
|
|
40
|
+
The `scan-artifact` command starts an asynchronous scan and reports an unknown final outcome until its status is checked. `create-tag` proves the tag was absent and checks that it appeared after the write. `delete-tag` requires an exact mutable tag on the specified digest, previews the target and confirms absence after deletion. Neither tag write mints an undo token because immutability and later artifact changes can make the inverse unsafe. Retention execution, replication restart, GC start/stop, artifact deletion and policy changes remain outside this release until their actual outcomes and recovery paths can be verified on disposable Harbor. See [verification](docs/VERIFICATION.md). Missing operations and live-environment feedback are welcome as issues or PRs.
|
|
41
|
+
|
|
42
|
+
## Development
|
|
43
|
+
|
|
44
|
+
`uv run pytest -q`, `uv run ruff check .`, and `uv run bandit -r harbor_aiops -ll` are local checks. Root handbook gates must pass before release.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
Report vulnerabilities privately through the repository's GitHub security advisory flow when the repository is available. Do not put credentials, registry URLs with embedded secrets, or private image details into public issues.
|
|
4
|
+
|
|
5
|
+
Credentials are stored in the encrypted local `secrets.enc` store. The config file contains only target location and username. TLS verification is on by default, and environment proxy settings are not inherited for registry requests. The bundled governance harness records audit metadata and enforces budgets. Use a Harbor account with the least server-side privileges needed for the requested queries and optional scan/tag writes. Scan, tag creation and high-risk single-tag deletion use dry-run; deletion requires exact target and mutable-status checks, CLI double confirmation, and a post-read. A lost response or failed post-read is marked unknown. Job logs and vulnerability details may contain sensitive data, so share their output only with intended operators.
|
|
6
|
+
|
|
7
|
+
The Docker build context and image copy include only packaging files and `harbor_aiops/`; local config, encrypted secrets, audit databases and virtual environments are excluded.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Verification — Harbor AIops
|
|
2
|
+
|
|
3
|
+
## Source-backed API surface
|
|
4
|
+
|
|
5
|
+
The Harbor [v2.0 Swagger schema](https://github.com/goharbor/harbor/blob/main/api/v2.0/swagger.yaml), checked 2026-11-04, defines the project/repository/artifact/tag, quota, GC, replication, retention and project-log paths used here, plus `/statistics`, project members, immutable-tag rules, robot accounts, labels, both scan-metrics endpoints and artifact scan logs. It explicitly requires a slash in a `repository_name` path argument to be encoded twice. GC, replication-task and retention-task logs produce `text/plain`. Artifact scan is `POST .../scan` with HTTP 202; tag creation is `POST .../tags` with HTTP 201. The schema defines exact tag deletion as `DELETE .../tags/{tag_name}` with HTTP 200. Collection responses use `page` and `page_size`; the implementation reads an extra row to measure truncation.
|
|
6
|
+
|
|
7
|
+
## Local verification
|
|
8
|
+
|
|
9
|
+
`tests/test_registry.py` uses a strict HTTP transport fake based on these paths and a loopback HTTP server for proxy isolation. It covers authentication, multi-page GC history, exact-limit results, 403 responses, repository double encoding, bounded plain-text logs, dry-run tag creation, post-read tag verification, exact mutable-tag deletion with dry-run and unknown post-check outcomes, robot-secret redaction, new read endpoint shapes, async scan uncertainty, lost write response, HTTP 500 ambiguity, redirect rejection and a partially failing diagnosis. Governance invariant tests come from the current tool line. Current result: 146 passing tests, Ruff clean, Bandit 0 Medium+ and wheel/sdist build, rerun on 2026-11-05.
|
|
10
|
+
|
|
11
|
+
The current 0.2.0 candidate wheel was rebuilt and installed into a new environment outside the source tree; its MCP registry reports 39 tools. Its `delete-tag` CLI ran preview and real calls against a strict loopback Harbor HTTP stub; preview made no DELETE, the real call made exactly one DELETE and reported success only after the tag GET returned 404. An earlier 0.2.0 candidate wheel was also installed outside the source tree; `harbor_aiops.__file__` resolved under `site-packages`. Its CLI ran four cases against a strict loopback HTTP stub: health, project listing, tag preview and real tag creation. The stub saw exactly one POST; the real create returned success only after a confirming GET. This validates packaging and request shape, not a real Harbor server.
|
|
12
|
+
|
|
13
|
+
## Not yet live verified
|
|
14
|
+
|
|
15
|
+
No Harbor instance was available: Docker is installed but its daemon was not running. No real Harbor API response, permissions matrix, asynchronous job completion, GC failure, retention dry-run, or write cycle has been verified. These writes record no undo token; deleting a tag cannot be safely reversed after it may be rebound or become immutable. Do not describe the package as production validated. The next gate is a disposable Harbor deployment with deliberately failed replication/retention and comparison to Harbor's own UI/API.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Shared helpers for harbor-aiops CLI sub-modules."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import functools
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from typing import Annotated, Any
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.markup import escape
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
# ─── Shared Option types ───────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
TargetOption = Annotated[
|
|
18
|
+
str | None, typer.Option("--target", "-t", help="Target name from config")
|
|
19
|
+
]
|
|
20
|
+
DryRunOption = Annotated[
|
|
21
|
+
bool, typer.Option("--dry-run", help="Print the API call without executing")
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _cli_error_types() -> tuple[type[BaseException], ...]:
|
|
26
|
+
"""Exceptions translated to a one-line teaching error instead of a traceback.
|
|
27
|
+
|
|
28
|
+
``BudgetExceeded`` (and the retained ``PolicyDenied`` type) are raised by
|
|
29
|
+
``@governed_tool`` OUTSIDE the tool body, so ``tool_errors`` never sees them
|
|
30
|
+
and they never arrive as an ``{"error": ...}`` dict. Their message is the
|
|
31
|
+
teaching text (e.g. which budget was hit) — without them here such a refusal
|
|
32
|
+
reaches the CLI as a traceback instead.
|
|
33
|
+
"""
|
|
34
|
+
from harbor_aiops.connection import HarborApiError
|
|
35
|
+
from harbor_aiops.governance import BudgetExceeded, PolicyDenied
|
|
36
|
+
from harbor_aiops.secretstore import SecretStoreError
|
|
37
|
+
|
|
38
|
+
return (HarborApiError, PolicyDenied, BudgetExceeded, SecretStoreError, KeyError, OSError,
|
|
39
|
+
ValueError)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def cli_errors(fn: Callable) -> Callable:
|
|
43
|
+
"""Translate known exceptions into one red line + exit code 1."""
|
|
44
|
+
|
|
45
|
+
@functools.wraps(fn)
|
|
46
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
47
|
+
try:
|
|
48
|
+
return fn(*args, **kwargs)
|
|
49
|
+
except (typer.Exit, typer.Abort):
|
|
50
|
+
raise
|
|
51
|
+
except _cli_error_types() as e:
|
|
52
|
+
message = str(e)
|
|
53
|
+
if isinstance(e, KeyError):
|
|
54
|
+
message = f"Missing required key or environment variable: {message}"
|
|
55
|
+
# Upstream text can contain "[/bold]"; unescaped, rich raises on it and the
|
|
56
|
+
# real error is replaced by a MarkupError traceback.
|
|
57
|
+
console.print(f"[red]Error: {escape(message)}[/]")
|
|
58
|
+
raise typer.Exit(1) from e
|
|
59
|
+
|
|
60
|
+
return wrapper
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
#: Exit status for a write whose outcome could not be determined — kept distinct
|
|
64
|
+
#: from 0 (confirmed) and 1 (failed) so a script can tell all three apart.
|
|
65
|
+
EXIT_UNDETERMINED = 2
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def governed(result: Any) -> dict:
|
|
69
|
+
"""Return a governed tool's result, or print its error and exit 1.
|
|
70
|
+
|
|
71
|
+
The ``harbor_aiops.mcp_server.tools`` twins never raise: ``@tool_errors`` flattens every
|
|
72
|
+
failure — an unreachable service, a refused login, a bad argument — into
|
|
73
|
+
``{"error": ...}``. A CLI command that drops that on the floor prints an empty
|
|
74
|
+
table and exits 0 as if the service had nothing to report. Route every governed
|
|
75
|
+
call through here.
|
|
76
|
+
"""
|
|
77
|
+
# ``outcomeUnknown`` is judged BEFORE ``error``, matching the harness: a
|
|
78
|
+
# write whose response was lost carries BOTH keys, and it is audited
|
|
79
|
+
# `unknown` precisely because it may have taken effect. Reporting that as a
|
|
80
|
+
# plain failure would tell a script the change did not happen and invite the
|
|
81
|
+
# double-apply the payload's own note warns about.
|
|
82
|
+
if isinstance(result, dict) and result.get("outcomeUnknown"):
|
|
83
|
+
console.print(f"[yellow]Outcome undetermined: {escape(str(result.get('note') or ''))}[/]")
|
|
84
|
+
raise typer.Exit(EXIT_UNDETERMINED)
|
|
85
|
+
if isinstance(result, dict) and result.get("error"):
|
|
86
|
+
console.print(f"[red]Error: {escape(str(result['error']))}[/]")
|
|
87
|
+
raise typer.Exit(1)
|
|
88
|
+
return result if isinstance(result, dict) else {}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def dry_run_print(*, operation: str, api_call: str, parameters: dict | None = None) -> None:
|
|
92
|
+
"""Print a dry-run preview of the API call that would be made."""
|
|
93
|
+
console.print("\n[bold magenta][DRY-RUN] No changes will be made.[/]")
|
|
94
|
+
console.print(f"[magenta] Operation: {operation}[/]")
|
|
95
|
+
console.print(f"[magenta] API Call: {api_call}[/]")
|
|
96
|
+
for k, v in (parameters or {}).items():
|
|
97
|
+
console.print(f"[magenta] Param: {k} = {v}[/]")
|
|
98
|
+
console.print("[magenta] Run without --dry-run to execute.[/]\n")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def double_confirm(action: str, resource: str) -> None:
|
|
102
|
+
"""Require two confirmations for a destructive operation."""
|
|
103
|
+
console.print(f"[bold yellow]⚠️ About to: {action} '{resource}'[/]")
|
|
104
|
+
typer.confirm(f"Confirm 1/2: {action} '{resource}'?", abort=True)
|
|
105
|
+
typer.confirm(
|
|
106
|
+
f"Confirm 2/2: really {action} '{resource}'? This may be irreversible.",
|
|
107
|
+
abort=True,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def print_truncation_note(result: dict, noun: str) -> None:
|
|
112
|
+
"""Print a visible warning when a listing envelope was capped.
|
|
113
|
+
|
|
114
|
+
The JSON already carries ``truncated``, but a human scrolling a long dump
|
|
115
|
+
will not spot it — and neither will a model summarising the terminal. Say
|
|
116
|
+
it in words, on the last line, where it cannot be missed.
|
|
117
|
+
"""
|
|
118
|
+
if isinstance(result, dict) and result.get("truncated"):
|
|
119
|
+
console.print(
|
|
120
|
+
f"[yellow]… showing {result.get('returned')} of more {noun} — "
|
|
121
|
+
f"truncated, re-run with a higher --limit[/]"
|
|
122
|
+
)
|