kubechain 0.1.0__py3-none-any.whl
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.
- kubechain/__init__.py +3 -0
- kubechain/__main__.py +5 -0
- kubechain/catalog.py +152 -0
- kubechain/check.py +933 -0
- kubechain/cli.py +98 -0
- kubechain/clients.json +210 -0
- kubechain/live.py +154 -0
- kubechain/load.py +50 -0
- kubechain/model.py +71 -0
- kubechain/render.py +64 -0
- kubechain-0.1.0.dist-info/METADATA +135 -0
- kubechain-0.1.0.dist-info/RECORD +16 -0
- kubechain-0.1.0.dist-info/WHEEL +5 -0
- kubechain-0.1.0.dist-info/entry_points.txt +2 -0
- kubechain-0.1.0.dist-info/licenses/LICENSE +21 -0
- kubechain-0.1.0.dist-info/top_level.txt +1 -0
kubechain/__init__.py
ADDED
kubechain/__main__.py
ADDED
kubechain/catalog.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Built-in client profiles and a local file that overrides one of them."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from dataclasses import dataclass, replace
|
|
7
|
+
from importlib.resources import files
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ProfileError(Exception):
|
|
12
|
+
"""The profile file cannot be used."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
ALLOWED = {
|
|
16
|
+
"comm",
|
|
17
|
+
"role",
|
|
18
|
+
"images",
|
|
19
|
+
"p2p_ports",
|
|
20
|
+
"rpc_ports",
|
|
21
|
+
"engine_port",
|
|
22
|
+
"jwt",
|
|
23
|
+
"data_volume",
|
|
24
|
+
"cpu",
|
|
25
|
+
"memory",
|
|
26
|
+
"storage",
|
|
27
|
+
"grace_seconds",
|
|
28
|
+
"exempt_probes",
|
|
29
|
+
"source",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class Profile:
|
|
35
|
+
comm: str
|
|
36
|
+
role: str
|
|
37
|
+
images: tuple[str, ...]
|
|
38
|
+
p2p_ports: tuple[int, ...]
|
|
39
|
+
rpc_ports: tuple[int, ...]
|
|
40
|
+
engine_port: int | None
|
|
41
|
+
jwt: bool
|
|
42
|
+
data_volume: str
|
|
43
|
+
cpu: str
|
|
44
|
+
memory: str
|
|
45
|
+
storage: str
|
|
46
|
+
grace_seconds: int
|
|
47
|
+
exempt_probes: bool
|
|
48
|
+
source: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def catalog() -> dict[str, Profile]:
|
|
52
|
+
raw = json.loads(files("kubechain").joinpath("clients.json").read_text())
|
|
53
|
+
return {name: _profile(body) for name, body in raw.items()}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def load_profile(path: Path) -> dict:
|
|
57
|
+
try:
|
|
58
|
+
data = json.loads(path.read_text())
|
|
59
|
+
except json.JSONDecodeError as exc:
|
|
60
|
+
raise ProfileError(f"profile is not JSON: {path}") from exc
|
|
61
|
+
if not isinstance(data, dict):
|
|
62
|
+
raise ProfileError("profile must be a JSON object")
|
|
63
|
+
extra = sorted(set(data) - ALLOWED)
|
|
64
|
+
if extra:
|
|
65
|
+
raise ProfileError(f"unknown profile field: {extra[0]}")
|
|
66
|
+
if "comm" not in data:
|
|
67
|
+
raise ProfileError("profile needs comm")
|
|
68
|
+
return data
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def resolve(container: dict, profiles: dict[str, Profile], overlay: dict | None) -> Profile | None:
|
|
72
|
+
name = str(container.get("name") or "")
|
|
73
|
+
image = str(container.get("image") or "")
|
|
74
|
+
chosen = profiles.get(name) or _by_image(image, profiles)
|
|
75
|
+
if overlay and overlay.get("comm") == name:
|
|
76
|
+
return _merge(chosen, overlay)
|
|
77
|
+
return chosen
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _by_image(image: str, profiles: dict[str, Profile]) -> Profile | None:
|
|
81
|
+
path = image.split("@", 1)[0]
|
|
82
|
+
slash = path.rfind("/")
|
|
83
|
+
colon = path.rfind(":")
|
|
84
|
+
if colon > slash:
|
|
85
|
+
path = path[:colon]
|
|
86
|
+
segments = path.split("/")
|
|
87
|
+
best: Profile | None = None
|
|
88
|
+
best_len = -1
|
|
89
|
+
for profile in profiles.values():
|
|
90
|
+
for hint in profile.images:
|
|
91
|
+
if hint in segments and len(hint) > best_len:
|
|
92
|
+
best = profile
|
|
93
|
+
best_len = len(hint)
|
|
94
|
+
return best
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _merge(base: Profile | None, overlay: dict) -> Profile:
|
|
98
|
+
if base is None:
|
|
99
|
+
base = Profile(
|
|
100
|
+
comm=str(overlay["comm"]),
|
|
101
|
+
role=str(overlay.get("role") or "unknown"),
|
|
102
|
+
images=(),
|
|
103
|
+
p2p_ports=(),
|
|
104
|
+
rpc_ports=(),
|
|
105
|
+
engine_port=None,
|
|
106
|
+
jwt=bool(overlay.get("jwt", False)),
|
|
107
|
+
data_volume=str(overlay.get("data_volume") or ""),
|
|
108
|
+
cpu=str(overlay.get("cpu") or ""),
|
|
109
|
+
memory=str(overlay.get("memory") or ""),
|
|
110
|
+
storage=str(overlay.get("storage") or ""),
|
|
111
|
+
grace_seconds=int(overlay.get("grace_seconds") or 30),
|
|
112
|
+
exempt_probes=bool(overlay.get("exempt_probes", False)),
|
|
113
|
+
source="Local profile.",
|
|
114
|
+
)
|
|
115
|
+
values = {}
|
|
116
|
+
for key in ("role", "data_volume", "cpu", "memory", "storage", "source"):
|
|
117
|
+
if key in overlay:
|
|
118
|
+
values[key] = overlay[key]
|
|
119
|
+
if "engine_port" in overlay:
|
|
120
|
+
values["engine_port"] = overlay["engine_port"]
|
|
121
|
+
if "jwt" in overlay:
|
|
122
|
+
values["jwt"] = bool(overlay["jwt"])
|
|
123
|
+
if "exempt_probes" in overlay:
|
|
124
|
+
values["exempt_probes"] = bool(overlay["exempt_probes"])
|
|
125
|
+
if "grace_seconds" in overlay:
|
|
126
|
+
values["grace_seconds"] = int(overlay["grace_seconds"])
|
|
127
|
+
if "p2p_ports" in overlay:
|
|
128
|
+
values["p2p_ports"] = tuple(int(port) for port in overlay["p2p_ports"])
|
|
129
|
+
if "rpc_ports" in overlay:
|
|
130
|
+
values["rpc_ports"] = tuple(int(port) for port in overlay["rpc_ports"])
|
|
131
|
+
if "images" in overlay:
|
|
132
|
+
values["images"] = tuple(overlay["images"])
|
|
133
|
+
return replace(base, **values)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _profile(body: dict) -> Profile:
|
|
137
|
+
return Profile(
|
|
138
|
+
comm=body["comm"],
|
|
139
|
+
role=body["role"],
|
|
140
|
+
images=tuple(body.get("images") or ()),
|
|
141
|
+
p2p_ports=tuple(body.get("p2p_ports") or ()),
|
|
142
|
+
rpc_ports=tuple(body.get("rpc_ports") or ()),
|
|
143
|
+
engine_port=body.get("engine_port"),
|
|
144
|
+
jwt=bool(body.get("jwt")),
|
|
145
|
+
data_volume=body.get("data_volume") or "",
|
|
146
|
+
cpu=body.get("cpu") or "",
|
|
147
|
+
memory=body.get("memory") or "",
|
|
148
|
+
storage=body.get("storage") or "",
|
|
149
|
+
grace_seconds=int(body.get("grace_seconds") or 30),
|
|
150
|
+
exempt_probes=bool(body.get("exempt_probes")),
|
|
151
|
+
source=body.get("source") or "KubeChain floor.",
|
|
152
|
+
)
|