k8s-aiops 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.
- k8s_aiops-0.1.0/.gitignore +22 -0
- k8s_aiops-0.1.0/LICENSE +21 -0
- k8s_aiops-0.1.0/PKG-INFO +99 -0
- k8s_aiops-0.1.0/README.md +84 -0
- k8s_aiops-0.1.0/SECURITY.md +78 -0
- k8s_aiops-0.1.0/k8s_aiops/__init__.py +10 -0
- k8s_aiops-0.1.0/k8s_aiops/cli/__init__.py +9 -0
- k8s_aiops-0.1.0/k8s_aiops/cli/_common.py +81 -0
- k8s_aiops-0.1.0/k8s_aiops/cli/_root.py +78 -0
- k8s_aiops-0.1.0/k8s_aiops/cli/deployment.py +95 -0
- k8s_aiops-0.1.0/k8s_aiops/cli/doctor.py +21 -0
- k8s_aiops-0.1.0/k8s_aiops/cli/namespace.py +27 -0
- k8s_aiops-0.1.0/k8s_aiops/cli/node.py +61 -0
- k8s_aiops-0.1.0/k8s_aiops/cli/pod.py +80 -0
- k8s_aiops-0.1.0/k8s_aiops/cli/service.py +32 -0
- k8s_aiops-0.1.0/k8s_aiops/config.py +113 -0
- k8s_aiops-0.1.0/k8s_aiops/connection.py +169 -0
- k8s_aiops-0.1.0/k8s_aiops/doctor.py +54 -0
- k8s_aiops-0.1.0/k8s_aiops/governance/__init__.py +40 -0
- k8s_aiops-0.1.0/k8s_aiops/governance/audit.py +377 -0
- k8s_aiops-0.1.0/k8s_aiops/governance/budget.py +225 -0
- k8s_aiops-0.1.0/k8s_aiops/governance/decorators.py +474 -0
- k8s_aiops-0.1.0/k8s_aiops/governance/paths.py +23 -0
- k8s_aiops-0.1.0/k8s_aiops/governance/patterns.py +378 -0
- k8s_aiops-0.1.0/k8s_aiops/governance/policy.py +411 -0
- k8s_aiops-0.1.0/k8s_aiops/governance/sanitize.py +39 -0
- k8s_aiops-0.1.0/k8s_aiops/governance/undo.py +218 -0
- k8s_aiops-0.1.0/k8s_aiops/ops/__init__.py +1 -0
- k8s_aiops-0.1.0/k8s_aiops/ops/_shared.py +48 -0
- k8s_aiops-0.1.0/k8s_aiops/ops/lifecycle.py +128 -0
- k8s_aiops-0.1.0/k8s_aiops/ops/namespaces.py +26 -0
- k8s_aiops-0.1.0/k8s_aiops/ops/nodes.py +49 -0
- k8s_aiops-0.1.0/k8s_aiops/ops/workloads.py +185 -0
- k8s_aiops-0.1.0/mcp_server/__init__.py +1 -0
- k8s_aiops-0.1.0/mcp_server/_shared.py +98 -0
- k8s_aiops-0.1.0/mcp_server/server.py +33 -0
- k8s_aiops-0.1.0/mcp_server/tools/__init__.py +1 -0
- k8s_aiops-0.1.0/mcp_server/tools/lifecycle.py +99 -0
- k8s_aiops-0.1.0/mcp_server/tools/namespaces.py +19 -0
- k8s_aiops-0.1.0/mcp_server/tools/nodes.py +66 -0
- k8s_aiops-0.1.0/mcp_server/tools/workloads.py +134 -0
- k8s_aiops-0.1.0/pyproject.toml +58 -0
- k8s_aiops-0.1.0/server.json +21 -0
- k8s_aiops-0.1.0/skills/k8s-aiops/SKILL.md +164 -0
- k8s_aiops-0.1.0/skills/k8s-aiops/references/capabilities.md +46 -0
- k8s_aiops-0.1.0/skills/k8s-aiops/references/cli-reference.md +63 -0
- k8s_aiops-0.1.0/skills/k8s-aiops/references/setup-guide.md +102 -0
- k8s_aiops-0.1.0/tests/test_smoke.py +243 -0
|
@@ -0,0 +1,22 @@
|
|
|
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
|
k8s_aiops-0.1.0/LICENSE
ADDED
|
@@ -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.
|
k8s_aiops-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: k8s-aiops
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Governed Kubernetes operations for AI agents with a built-in governance harness (audit, budget, undo, risk tiers)
|
|
5
|
+
Author-email: wei <zhouwei008@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: kubernetes<32,>=29
|
|
10
|
+
Requires-Dist: mcp[cli]<2.0,>=1.10
|
|
11
|
+
Requires-Dist: pyyaml<7.0,>=6.0
|
|
12
|
+
Requires-Dist: rich<16.0,>=13.0
|
|
13
|
+
Requires-Dist: typer<1.0,>=0.12
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
<!-- mcp-name: io.github.AIops-tools/k8s-aiops -->
|
|
17
|
+
# k8s-aiops (preview)
|
|
18
|
+
|
|
19
|
+
> **Disclaimer**: This is a community-maintained open-source project and is **not
|
|
20
|
+
> affiliated with, endorsed by, or sponsored by the Cloud Native Computing
|
|
21
|
+
> Foundation, the Kubernetes project, or k3s/Rancher.** "Kubernetes" and "k3s" are
|
|
22
|
+
> trademarks of their respective owners. Source code is publicly auditable at
|
|
23
|
+
> [github.com/AIops-tools/K8s-AIops](https://github.com/AIops-tools/K8s-AIops) under
|
|
24
|
+
> the MIT license.
|
|
25
|
+
|
|
26
|
+
Governed Kubernetes operations for AI agents — **15 MCP tools**, every one wrapped
|
|
27
|
+
with the bundled `@governed_tool` harness: a local unified audit log under
|
|
28
|
+
`~/.k8s-aiops/`, policy engine, token/runaway budget guard, undo-token recording, and
|
|
29
|
+
graduated-autonomy risk tiers.
|
|
30
|
+
|
|
31
|
+
> **Standalone**: the governance harness is bundled in the package
|
|
32
|
+
> (`k8s_aiops.governance`) — k8s-aiops has no external skill-family dependency.
|
|
33
|
+
> Preview: common cluster operations, not yet exhaustive.
|
|
34
|
+
|
|
35
|
+
## What works
|
|
36
|
+
|
|
37
|
+
Any cluster a kubeconfig can reach: standard Kubernetes, **k3s**, **EKS**, **GKE**,
|
|
38
|
+
**AKS**, kind, minikube. Authentication (client certs, tokens, EKS/GKE/AKS exec
|
|
39
|
+
plugins) is delegated entirely to the kubeconfig.
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uv tool install k8s-aiops
|
|
45
|
+
|
|
46
|
+
# Uses your current kube-context out of the box:
|
|
47
|
+
k8s-aiops doctor
|
|
48
|
+
k8s-aiops pod list
|
|
49
|
+
k8s-aiops deployment list -n default
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
To define named targets (multiple clusters/contexts), create
|
|
53
|
+
`~/.k8s-aiops/config.yaml`:
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
targets:
|
|
57
|
+
- name: prod # used as -t prod
|
|
58
|
+
context: prod-eks # a context in your kubeconfig (omit for current-context)
|
|
59
|
+
namespace: default # optional default namespace
|
|
60
|
+
# kubeconfig: /path/to/alt/kubeconfig # optional explicit path
|
|
61
|
+
- name: lab
|
|
62
|
+
context: k3s-lab
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
No secrets live in this file — credentials come from the kubeconfig.
|
|
66
|
+
|
|
67
|
+
## MCP
|
|
68
|
+
|
|
69
|
+
```jsonc
|
|
70
|
+
{
|
|
71
|
+
"command": "k8s-aiops",
|
|
72
|
+
"args": ["mcp"],
|
|
73
|
+
"env": { "K8S_AIOPS_CONFIG": "~/.k8s-aiops/config.yaml" }
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Audit & Safety
|
|
78
|
+
|
|
79
|
+
- Every tool call is logged to `~/.k8s-aiops/audit.db` (local SQLite; relocate with
|
|
80
|
+
`K8S_AIOPS_HOME`).
|
|
81
|
+
- Reversible writes record an inverse undo descriptor (`scale_deployment` →
|
|
82
|
+
scale-back to previous; `cordon_node` ↔ `uncordon_node`).
|
|
83
|
+
- `delete_deployment` is `risk_level=high`; CLI destructive commands require double
|
|
84
|
+
confirmation and support `--dry-run`.
|
|
85
|
+
- All API text passes through `sanitize()` (prompt-injection defense).
|
|
86
|
+
|
|
87
|
+
See `skills/k8s-aiops/SKILL.md` and `SECURITY.md` for details.
|
|
88
|
+
|
|
89
|
+
## Companion Skills
|
|
90
|
+
|
|
91
|
+
| If you want… | Use |
|
|
92
|
+
|--------------|-----|
|
|
93
|
+
| Kubernetes pods / deployments / nodes | **k8s-aiops** (this) |
|
|
94
|
+
| Hypervisor VM lifecycle | a hypervisor ops skill |
|
|
95
|
+
| Backup & restore | a backup ops skill |
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT — [github.com/AIops-tools/K8s-AIops](https://github.com/AIops-tools/K8s-AIops)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<!-- mcp-name: io.github.AIops-tools/k8s-aiops -->
|
|
2
|
+
# k8s-aiops (preview)
|
|
3
|
+
|
|
4
|
+
> **Disclaimer**: This is a community-maintained open-source project and is **not
|
|
5
|
+
> affiliated with, endorsed by, or sponsored by the Cloud Native Computing
|
|
6
|
+
> Foundation, the Kubernetes project, or k3s/Rancher.** "Kubernetes" and "k3s" are
|
|
7
|
+
> trademarks of their respective owners. Source code is publicly auditable at
|
|
8
|
+
> [github.com/AIops-tools/K8s-AIops](https://github.com/AIops-tools/K8s-AIops) under
|
|
9
|
+
> the MIT license.
|
|
10
|
+
|
|
11
|
+
Governed Kubernetes operations for AI agents — **15 MCP tools**, every one wrapped
|
|
12
|
+
with the bundled `@governed_tool` harness: a local unified audit log under
|
|
13
|
+
`~/.k8s-aiops/`, policy engine, token/runaway budget guard, undo-token recording, and
|
|
14
|
+
graduated-autonomy risk tiers.
|
|
15
|
+
|
|
16
|
+
> **Standalone**: the governance harness is bundled in the package
|
|
17
|
+
> (`k8s_aiops.governance`) — k8s-aiops has no external skill-family dependency.
|
|
18
|
+
> Preview: common cluster operations, not yet exhaustive.
|
|
19
|
+
|
|
20
|
+
## What works
|
|
21
|
+
|
|
22
|
+
Any cluster a kubeconfig can reach: standard Kubernetes, **k3s**, **EKS**, **GKE**,
|
|
23
|
+
**AKS**, kind, minikube. Authentication (client certs, tokens, EKS/GKE/AKS exec
|
|
24
|
+
plugins) is delegated entirely to the kubeconfig.
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
uv tool install k8s-aiops
|
|
30
|
+
|
|
31
|
+
# Uses your current kube-context out of the box:
|
|
32
|
+
k8s-aiops doctor
|
|
33
|
+
k8s-aiops pod list
|
|
34
|
+
k8s-aiops deployment list -n default
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
To define named targets (multiple clusters/contexts), create
|
|
38
|
+
`~/.k8s-aiops/config.yaml`:
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
targets:
|
|
42
|
+
- name: prod # used as -t prod
|
|
43
|
+
context: prod-eks # a context in your kubeconfig (omit for current-context)
|
|
44
|
+
namespace: default # optional default namespace
|
|
45
|
+
# kubeconfig: /path/to/alt/kubeconfig # optional explicit path
|
|
46
|
+
- name: lab
|
|
47
|
+
context: k3s-lab
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
No secrets live in this file — credentials come from the kubeconfig.
|
|
51
|
+
|
|
52
|
+
## MCP
|
|
53
|
+
|
|
54
|
+
```jsonc
|
|
55
|
+
{
|
|
56
|
+
"command": "k8s-aiops",
|
|
57
|
+
"args": ["mcp"],
|
|
58
|
+
"env": { "K8S_AIOPS_CONFIG": "~/.k8s-aiops/config.yaml" }
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Audit & Safety
|
|
63
|
+
|
|
64
|
+
- Every tool call is logged to `~/.k8s-aiops/audit.db` (local SQLite; relocate with
|
|
65
|
+
`K8S_AIOPS_HOME`).
|
|
66
|
+
- Reversible writes record an inverse undo descriptor (`scale_deployment` →
|
|
67
|
+
scale-back to previous; `cordon_node` ↔ `uncordon_node`).
|
|
68
|
+
- `delete_deployment` is `risk_level=high`; CLI destructive commands require double
|
|
69
|
+
confirmation and support `--dry-run`.
|
|
70
|
+
- All API text passes through `sanitize()` (prompt-injection defense).
|
|
71
|
+
|
|
72
|
+
See `skills/k8s-aiops/SKILL.md` and `SECURITY.md` for details.
|
|
73
|
+
|
|
74
|
+
## Companion Skills
|
|
75
|
+
|
|
76
|
+
| If you want… | Use |
|
|
77
|
+
|--------------|-----|
|
|
78
|
+
| Kubernetes pods / deployments / nodes | **k8s-aiops** (this) |
|
|
79
|
+
| Hypervisor VM lifecycle | a hypervisor ops skill |
|
|
80
|
+
| Backup & restore | a backup ops skill |
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
MIT — [github.com/AIops-tools/K8s-AIops](https://github.com/AIops-tools/K8s-AIops)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Disclaimer
|
|
4
|
+
|
|
5
|
+
This is a community-maintained open-source project and is **not affiliated with,
|
|
6
|
+
endorsed by, or sponsored by the Cloud Native Computing Foundation, the Kubernetes
|
|
7
|
+
project, or k3s/Rancher.** "Kubernetes" and "k3s" are trademarks of their respective
|
|
8
|
+
owners. Source code is publicly auditable at
|
|
9
|
+
[github.com/AIops-tools/K8s-AIops](https://github.com/AIops-tools/K8s-AIops) under
|
|
10
|
+
the MIT license.
|
|
11
|
+
|
|
12
|
+
## Reporting Vulnerabilities
|
|
13
|
+
|
|
14
|
+
Report security issues privately to **zhouwei008@gmail.com** or via a GitHub
|
|
15
|
+
private security advisory on the repository. Please do not open public issues for
|
|
16
|
+
undisclosed vulnerabilities.
|
|
17
|
+
|
|
18
|
+
## Security Design
|
|
19
|
+
|
|
20
|
+
### Credential Management
|
|
21
|
+
|
|
22
|
+
k8s-aiops does **not** store or handle cluster credentials directly. All
|
|
23
|
+
authentication is delegated to the kubeconfig (`KUBECONFIG` env or `~/.kube/config`),
|
|
24
|
+
which may hold client certificates, bearer tokens, or exec plugins (for EKS/GKE/AKS).
|
|
25
|
+
The skill loads a kube context via the official `kubernetes` client and never reads,
|
|
26
|
+
logs, or echoes the underlying credentials. The state directory `~/.k8s-aiops` should
|
|
27
|
+
be owner-only (`chmod 700`); the skill warns if it is more permissive.
|
|
28
|
+
|
|
29
|
+
### Destructive Operation Safety
|
|
30
|
+
|
|
31
|
+
Write operations (scale, rollout restart, delete pod, delete deployment, cordon)
|
|
32
|
+
all pass through the bundled `@governed_tool` decorator: policy pre-check, token /
|
|
33
|
+
runaway budget guard, graduated-autonomy risk-tier gate, and audit logging. The CLI
|
|
34
|
+
layer additionally requires double confirmation and supports `--dry-run` for the
|
|
35
|
+
most destructive commands (deployment delete, pod delete, node cordon). Reversible
|
|
36
|
+
writes record an inverse undo descriptor — `scale_deployment` records a scale-back to
|
|
37
|
+
the previous replica count; `cordon_node` ↔ `uncordon_node` are mutual inverses.
|
|
38
|
+
`delete_pod`, `delete_deployment`, and `rollout_restart_deployment` declare no undo;
|
|
39
|
+
`delete_deployment` is tagged `risk_level=high`.
|
|
40
|
+
|
|
41
|
+
### Least Privilege
|
|
42
|
+
|
|
43
|
+
Use a kube context bound to a ServiceAccount or user with only the RBAC verbs you
|
|
44
|
+
need. Read-only use requires only `get`/`list`/`watch`; the write tools additionally
|
|
45
|
+
need `patch`/`delete` on the relevant resources.
|
|
46
|
+
|
|
47
|
+
### Webhooks / Outbound Network
|
|
48
|
+
|
|
49
|
+
None. The skill makes no outbound network calls beyond the configured Kubernetes API
|
|
50
|
+
server. There are no background services or post-install scripts.
|
|
51
|
+
|
|
52
|
+
### TLS Verification
|
|
53
|
+
|
|
54
|
+
TLS verification follows the kubeconfig (`certificate-authority` / `insecure-skip-tls-verify`).
|
|
55
|
+
The skill does not weaken it; disable verification only in the kubeconfig itself and
|
|
56
|
+
only for self-signed lab clusters.
|
|
57
|
+
|
|
58
|
+
### Prompt Injection Protection
|
|
59
|
+
|
|
60
|
+
All text returned from the Kubernetes API (names, log lines, event messages) is run
|
|
61
|
+
through `sanitize()` — truncation plus C0/C1 control-character stripping — before
|
|
62
|
+
reaching the agent.
|
|
63
|
+
|
|
64
|
+
### Transitive Dependencies
|
|
65
|
+
|
|
66
|
+
`kubernetes` (official Python client), `typer`/`rich` (CLI), `pyyaml` (config), and
|
|
67
|
+
the MCP SDK. No external skill-family dependency — the governance harness is vendored
|
|
68
|
+
under `k8s_aiops.governance`.
|
|
69
|
+
|
|
70
|
+
## Static Analysis
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
uvx bandit -r k8s_aiops/ mcp_server/
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Supported Versions
|
|
77
|
+
|
|
78
|
+
The latest released version (currently 0.1.0, preview) receives security fixes.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""k8s-aiops — governed Kubernetes operations for AI agents.
|
|
2
|
+
|
|
3
|
+
Standalone and self-contained: the governance harness (audit, token budget,
|
|
4
|
+
undo-token recording, graduated risk tiers, prompt-injection sanitize) is
|
|
5
|
+
bundled under ``k8s_aiops.governance`` — this package has no external
|
|
6
|
+
skill-family dependency. Works with any kubeconfig-reachable cluster
|
|
7
|
+
(standard Kubernetes, k3s, EKS, GKE, AKS). Preview: not yet full-coverage.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Shared helpers for k8s-aiops CLI sub-modules."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import functools
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Annotated, Any
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
from rich.console import Console
|
|
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
|
+
NamespaceOption = Annotated[
|
|
21
|
+
str | None, typer.Option("--namespace", "-n", help="Namespace (omit for all/default)")
|
|
22
|
+
]
|
|
23
|
+
DryRunOption = Annotated[
|
|
24
|
+
bool, typer.Option("--dry-run", help="Print the operation without executing")
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _cli_error_types() -> tuple[type[BaseException], ...]:
|
|
29
|
+
"""Exceptions translated to a one-line teaching error instead of a traceback."""
|
|
30
|
+
from k8s_aiops.connection import K8sApiError
|
|
31
|
+
|
|
32
|
+
return (K8sApiError, KeyError, OSError, ValueError)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def cli_errors(fn: Callable) -> Callable:
|
|
36
|
+
"""Translate known exceptions into one red line + exit code 1."""
|
|
37
|
+
|
|
38
|
+
@functools.wraps(fn)
|
|
39
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
40
|
+
try:
|
|
41
|
+
return fn(*args, **kwargs)
|
|
42
|
+
except (typer.Exit, typer.Abort):
|
|
43
|
+
raise
|
|
44
|
+
except _cli_error_types() as e:
|
|
45
|
+
message = str(e)
|
|
46
|
+
if isinstance(e, KeyError):
|
|
47
|
+
message = f"Missing required key: {message}"
|
|
48
|
+
console.print(f"[red]Error: {message}[/]")
|
|
49
|
+
raise typer.Exit(1) from e
|
|
50
|
+
|
|
51
|
+
return wrapper
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_connection(target: str | None, config_path: Path | None = None):
|
|
55
|
+
"""Return a (conn, config) tuple for the given target."""
|
|
56
|
+
from k8s_aiops.config import load_config
|
|
57
|
+
from k8s_aiops.connection import ConnectionManager
|
|
58
|
+
|
|
59
|
+
cfg = load_config(config_path)
|
|
60
|
+
mgr = ConnectionManager(cfg)
|
|
61
|
+
return mgr.connect(target), cfg
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def dry_run_print(*, operation: str, detail: str, parameters: dict | None = None) -> None:
|
|
65
|
+
"""Print a dry-run preview of the operation that would be performed."""
|
|
66
|
+
console.print("\n[bold magenta][DRY-RUN] No changes will be made.[/]")
|
|
67
|
+
console.print(f"[magenta] Operation: {operation}[/]")
|
|
68
|
+
console.print(f"[magenta] Detail: {detail}[/]")
|
|
69
|
+
for k, v in (parameters or {}).items():
|
|
70
|
+
console.print(f"[magenta] Param: {k} = {v}[/]")
|
|
71
|
+
console.print("[magenta] Run without --dry-run to execute.[/]\n")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def double_confirm(action: str, resource: str) -> None:
|
|
75
|
+
"""Require two confirmations for a destructive operation."""
|
|
76
|
+
console.print(f"[bold yellow]⚠️ About to: {action} '{resource}'[/]")
|
|
77
|
+
typer.confirm(f"Confirm 1/2: {action} '{resource}'?", abort=True)
|
|
78
|
+
typer.confirm(
|
|
79
|
+
f"Confirm 2/2: really {action} '{resource}'? This may be irreversible.",
|
|
80
|
+
abort=True,
|
|
81
|
+
)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Top-level Typer app: assembles sub-apps and top-level commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from k8s_aiops.cli._common import NamespaceOption, TargetOption, cli_errors
|
|
8
|
+
from k8s_aiops.cli.deployment import deployment_app
|
|
9
|
+
from k8s_aiops.cli.doctor import doctor_cmd
|
|
10
|
+
from k8s_aiops.cli.namespace import namespace_app
|
|
11
|
+
from k8s_aiops.cli.node import node_app
|
|
12
|
+
from k8s_aiops.cli.pod import pod_app
|
|
13
|
+
from k8s_aiops.cli.service import service_app
|
|
14
|
+
|
|
15
|
+
app = typer.Typer(
|
|
16
|
+
name="k8s-aiops",
|
|
17
|
+
help="Governed Kubernetes operations for AI agents (works with k3s/EKS/GKE/AKS).",
|
|
18
|
+
no_args_is_help=True,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
app.add_typer(pod_app, name="pod")
|
|
22
|
+
app.add_typer(deployment_app, name="deployment")
|
|
23
|
+
app.add_typer(service_app, name="service")
|
|
24
|
+
app.add_typer(node_app, name="node")
|
|
25
|
+
app.add_typer(namespace_app, name="namespace")
|
|
26
|
+
app.command("doctor")(doctor_cmd)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.command("events")
|
|
30
|
+
@cli_errors
|
|
31
|
+
def events_cmd(target: TargetOption = None, namespace: NamespaceOption = None) -> None:
|
|
32
|
+
"""List recent cluster events (namespace-scoped or all-namespaces)."""
|
|
33
|
+
from rich.console import Console
|
|
34
|
+
from rich.table import Table
|
|
35
|
+
|
|
36
|
+
from k8s_aiops.cli._common import get_connection
|
|
37
|
+
from k8s_aiops.ops import workloads
|
|
38
|
+
|
|
39
|
+
conn, _ = get_connection(target)
|
|
40
|
+
rows = workloads.list_events(conn, namespace)
|
|
41
|
+
table = Table(title="Kubernetes Events")
|
|
42
|
+
for col in ("type", "reason", "object", "namespace", "message", "age"):
|
|
43
|
+
table.add_column(col)
|
|
44
|
+
for r in rows:
|
|
45
|
+
table.add_row(
|
|
46
|
+
r["type"], r["reason"], r["object"], r["namespace"], r["message"], r["age"]
|
|
47
|
+
)
|
|
48
|
+
Console().print(table)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@app.command("mcp")
|
|
52
|
+
@cli_errors
|
|
53
|
+
def mcp_cmd() -> None:
|
|
54
|
+
"""Start the MCP server (stdio transport).
|
|
55
|
+
|
|
56
|
+
Single-command entry point for MCP clients (does not go through uvx/PyPI
|
|
57
|
+
resolution at launch):
|
|
58
|
+
k8s-aiops mcp
|
|
59
|
+
"""
|
|
60
|
+
import sys
|
|
61
|
+
|
|
62
|
+
if sys.version_info < (3, 11):
|
|
63
|
+
typer.echo(
|
|
64
|
+
f"ERROR: k8s-aiops requires Python >= 3.11 "
|
|
65
|
+
f"(got {sys.version_info.major}.{sys.version_info.minor}).\n"
|
|
66
|
+
f"Fix: uv python install 3.12 && "
|
|
67
|
+
f"uv tool install --python 3.12 --force k8s-aiops",
|
|
68
|
+
err=True,
|
|
69
|
+
)
|
|
70
|
+
raise typer.Exit(2)
|
|
71
|
+
|
|
72
|
+
from mcp_server.server import main as _mcp_main
|
|
73
|
+
|
|
74
|
+
_mcp_main()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
app()
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""``k8s-aiops deployment ...`` sub-commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
|
|
9
|
+
from k8s_aiops.cli._common import (
|
|
10
|
+
DryRunOption,
|
|
11
|
+
NamespaceOption,
|
|
12
|
+
TargetOption,
|
|
13
|
+
cli_errors,
|
|
14
|
+
double_confirm,
|
|
15
|
+
dry_run_print,
|
|
16
|
+
get_connection,
|
|
17
|
+
)
|
|
18
|
+
from k8s_aiops.ops import lifecycle, workloads
|
|
19
|
+
|
|
20
|
+
deployment_app = typer.Typer(help="Deployment operations.", no_args_is_help=True)
|
|
21
|
+
console = Console()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@deployment_app.command("list")
|
|
25
|
+
@cli_errors
|
|
26
|
+
def deployment_list(target: TargetOption = None, namespace: NamespaceOption = None) -> None:
|
|
27
|
+
"""List deployments (name, namespace, desired/ready/available, age)."""
|
|
28
|
+
conn, _ = get_connection(target)
|
|
29
|
+
rows = workloads.list_deployments(conn, namespace)
|
|
30
|
+
table = Table(title="Deployments")
|
|
31
|
+
for col in ("name", "namespace", "desired", "ready", "available", "age"):
|
|
32
|
+
table.add_column(col)
|
|
33
|
+
for r in rows:
|
|
34
|
+
table.add_row(
|
|
35
|
+
r["name"], r["namespace"], str(r["desired"]),
|
|
36
|
+
str(r["ready"]), str(r["available"]), r["age"],
|
|
37
|
+
)
|
|
38
|
+
console.print(table)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@deployment_app.command("get")
|
|
42
|
+
@cli_errors
|
|
43
|
+
def deployment_get(
|
|
44
|
+
name: str, target: TargetOption = None, namespace: NamespaceOption = None
|
|
45
|
+
) -> None:
|
|
46
|
+
"""Show detail for one deployment."""
|
|
47
|
+
conn, _ = get_connection(target)
|
|
48
|
+
for k, v in workloads.get_deployment(conn, name, namespace).items():
|
|
49
|
+
console.print(f" [cyan]{k}:[/] {v}")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@deployment_app.command("scale")
|
|
53
|
+
@cli_errors
|
|
54
|
+
def deployment_scale(
|
|
55
|
+
name: str,
|
|
56
|
+
replicas: int,
|
|
57
|
+
target: TargetOption = None,
|
|
58
|
+
namespace: NamespaceOption = None,
|
|
59
|
+
) -> None:
|
|
60
|
+
"""Scale a deployment to a replica count."""
|
|
61
|
+
conn, _ = get_connection(target)
|
|
62
|
+
result = lifecycle.scale_deployment(conn, name, replicas, namespace)
|
|
63
|
+
console.print(
|
|
64
|
+
f"[green]Scaled {name} -> {replicas}[/] "
|
|
65
|
+
f"(was {result['previous_replicas']})"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@deployment_app.command("restart")
|
|
70
|
+
@cli_errors
|
|
71
|
+
def deployment_restart(
|
|
72
|
+
name: str, target: TargetOption = None, namespace: NamespaceOption = None
|
|
73
|
+
) -> None:
|
|
74
|
+
"""Trigger a rolling restart of a deployment."""
|
|
75
|
+
conn, _ = get_connection(target)
|
|
76
|
+
lifecycle.rollout_restart_deployment(conn, name, namespace)
|
|
77
|
+
console.print(f"[green]Rollout restart triggered for {name}[/]")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@deployment_app.command("delete")
|
|
81
|
+
@cli_errors
|
|
82
|
+
def deployment_delete(
|
|
83
|
+
name: str,
|
|
84
|
+
target: TargetOption = None,
|
|
85
|
+
namespace: NamespaceOption = None,
|
|
86
|
+
dry_run: DryRunOption = False,
|
|
87
|
+
) -> None:
|
|
88
|
+
"""Delete a deployment and its pods (HIGH RISK — double confirm)."""
|
|
89
|
+
if dry_run:
|
|
90
|
+
dry_run_print(operation="delete_deployment", detail=f"delete deployment {name}")
|
|
91
|
+
return
|
|
92
|
+
double_confirm("delete", f"deployment {name}")
|
|
93
|
+
conn, _ = get_connection(target)
|
|
94
|
+
lifecycle.delete_deployment(conn, name, namespace)
|
|
95
|
+
console.print(f"[green]Deleted deployment {name}[/]")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Doctor top-level command: environment and connectivity check."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from k8s_aiops.cli._common import cli_errors
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@cli_errors
|
|
13
|
+
def doctor_cmd(
|
|
14
|
+
skip_auth: Annotated[
|
|
15
|
+
bool, typer.Option("--skip-auth", help="Skip connectivity check (faster)")
|
|
16
|
+
] = False,
|
|
17
|
+
) -> None:
|
|
18
|
+
"""Check config and cluster reachability."""
|
|
19
|
+
from k8s_aiops.doctor import run_doctor
|
|
20
|
+
|
|
21
|
+
raise typer.Exit(run_doctor(skip_auth=skip_auth))
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""``k8s-aiops namespace ...`` sub-commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
|
|
9
|
+
from k8s_aiops.cli._common import TargetOption, cli_errors, get_connection
|
|
10
|
+
from k8s_aiops.ops import namespaces
|
|
11
|
+
|
|
12
|
+
namespace_app = typer.Typer(help="Namespace operations.", no_args_is_help=True)
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@namespace_app.command("list")
|
|
17
|
+
@cli_errors
|
|
18
|
+
def namespace_list(target: TargetOption = None) -> None:
|
|
19
|
+
"""List namespaces (name, phase, age)."""
|
|
20
|
+
conn, _ = get_connection(target)
|
|
21
|
+
rows = namespaces.list_namespaces(conn)
|
|
22
|
+
table = Table(title="Namespaces")
|
|
23
|
+
for col in ("name", "phase", "age"):
|
|
24
|
+
table.add_column(col)
|
|
25
|
+
for r in rows:
|
|
26
|
+
table.add_row(r["name"], r["phase"], r["age"])
|
|
27
|
+
console.print(table)
|