Skip to content

Commit 029202e

Browse files
authored
Rollup kubectl commands to a common library (#31)
* Rollup kubectl commands to a common library * Apply review comments * Additional unit tests * Apply review comments
1 parent f4711b2 commit 029202e

File tree

10 files changed

+1242
-273
lines changed

10 files changed

+1242
-273
lines changed

src/kube_galaxy/cmd/status.py

Lines changed: 28 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
"""Status command handler."""
22

33
import shutil
4+
from collections.abc import Callable
45

56
import typer
67

8+
from kube_galaxy.pkg.utils.client import (
9+
get_cluster_info,
10+
get_context,
11+
get_nodes,
12+
get_pods,
13+
wait_for_nodes,
14+
wait_for_pods,
15+
)
16+
from kube_galaxy.pkg.utils.errors import ClusterError
717
from kube_galaxy.pkg.utils.logging import error, info, print_dict, section, success, warning
8-
from kube_galaxy.pkg.utils.shell import ShellError, run
18+
from kube_galaxy.pkg.utils.shell import run
919

1020

1121
def status(wait: bool = False, timeout: int = 300) -> None:
@@ -25,7 +35,6 @@ def _print_dependency_status() -> None:
2535
info("Dependencies:")
2636
deps = {
2737
"kubectl": _check_command("kubectl"),
28-
"kubeadm": _check_command("kubeadm"),
2938
"spread": _check_command("spread"),
3039
}
3140
print_dict(deps)
@@ -39,22 +48,17 @@ def _print_cluster_context() -> None:
3948

4049
info("")
4150
try:
42-
result = run(["kubectl", "config", "current-context"], capture_output=True, check=False)
43-
context = result.stdout.strip() if result.returncode == 0 else "none"
51+
context = get_context()
4452
info(f"Active Cluster: {context}")
45-
except Exception:
46-
info("Active Cluster: error checking")
47-
48-
try:
49-
result = run(["kubectl", "get", "nodes"], capture_output=True, check=False)
50-
if result.returncode == 0 and result.stdout:
51-
lines = result.stdout.strip().split("\n")
53+
nodes_output = get_nodes()
54+
if nodes_output:
55+
lines = nodes_output.strip().split("\n")
5256
info(f"Cluster Nodes: {len(lines) - 1}")
5357
for line in lines[1:]:
5458
if line:
5559
info(f" {line}")
56-
except Exception:
57-
pass
60+
except ClusterError:
61+
info("Active Cluster: error checking")
5862

5963

6064
def _verify_cluster_health(timeout: int) -> None:
@@ -63,52 +67,31 @@ def _verify_cluster_health(timeout: int) -> None:
6367
error("kubectl is required for --wait health checks", show_traceback=False)
6468
raise typer.Exit(code=1)
6569

66-
timeout_arg = f"--timeout={timeout}s"
6770
section("Cluster Health Verification")
6871
info("Waiting for nodes to be Ready...")
6972

7073
try:
71-
run(
72-
["kubectl", "wait", "--for=condition=Ready", "node", "--all", timeout_arg],
73-
capture_output=True,
74-
)
75-
run(
76-
[
77-
"kubectl",
78-
"wait",
79-
"--for=condition=Ready",
80-
"pod",
81-
"--all",
82-
"-n",
83-
"kube-system",
84-
timeout_arg,
85-
],
86-
capture_output=True,
87-
)
88-
except ShellError as exc:
89-
if exc.stderr.strip():
90-
error(exc.stderr.strip(), show_traceback=False)
74+
wait_for_nodes(timeout=timeout)
75+
wait_for_pods(namespace="kube-system", timeout=timeout)
76+
except ClusterError as exc:
77+
error(str(exc), show_traceback=False)
9178
error("Cluster readiness checks failed", show_traceback=False)
9279
raise typer.Exit(code=1) from exc
9380

94-
_print_command_output(["kubectl", "cluster-info"], "Cluster Info")
95-
_print_command_output(["kubectl", "get", "nodes", "-o", "wide"], "Nodes")
96-
_print_command_output(["kubectl", "get", "pods", "-A", "-o", "wide"], "Pods")
81+
_print_command_output(get_cluster_info, "Cluster Info")
82+
_print_command_output(get_nodes, "Nodes")
83+
_print_command_output(get_pods, "Pods")
9784

9885

99-
def _print_command_output(command: list[str], title: str) -> None:
86+
def _print_command_output(command: Callable[[], str], title: str) -> None:
10087
"""Run command and print its output with a section label."""
10188
info("")
10289
info(f"{title}:")
10390
try:
104-
result = run(command, capture_output=True)
105-
output = result.stdout.strip()
106-
if output:
91+
if output := command().strip():
10792
info(output)
108-
except ShellError as exc:
109-
if exc.stderr.strip():
110-
error(exc.stderr.strip(), show_traceback=False)
111-
error(f"Failed to run: {' '.join(command)}", show_traceback=False)
93+
except ClusterError as exc:
94+
error(f"Failed to run: {title}", show_traceback=False)
11295
raise typer.Exit(code=1) from exc
11396

11497

@@ -122,12 +105,6 @@ def _check_command(cmd: str) -> str:
122105
capture_output=True,
123106
check=False,
124107
)
125-
elif cmd == "kubeadm":
126-
result = run(
127-
[cmd, "version"],
128-
capture_output=True,
129-
check=False,
130-
)
131108
else:
132109
result = run(
133110
[cmd, "--version"],

src/kube_galaxy/cmd/test.py

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""Test command handler."""
22

3-
import subprocess
43
from pathlib import Path
54

65
import typer
@@ -9,6 +8,8 @@
98
from kube_galaxy.pkg.manifest.loader import load_manifest
109
from kube_galaxy.pkg.manifest.validator import validate_manifest
1110
from kube_galaxy.pkg.testing.spread import collect_test_results, run_spread_tests
11+
from kube_galaxy.pkg.utils.client import get_context, verify_connectivity
12+
from kube_galaxy.pkg.utils.errors import ClusterError
1213
from kube_galaxy.pkg.utils.logging import error, exception, info, section, success, warning
1314

1415

@@ -22,25 +23,10 @@ def spread(manifest_path: str) -> None:
2223

2324
try:
2425
# Check if kubectl can connect
25-
result = subprocess.run(
26-
["kubectl", "cluster-info"],
27-
capture_output=True,
28-
text=True,
29-
check=False,
30-
)
31-
if result.returncode != 0:
32-
error("No Kubernetes cluster available. Please set up a cluster first.")
33-
info("You can create a test cluster with: kube-galaxy setup")
34-
raise typer.Exit(code=1)
26+
verify_connectivity()
3527

3628
# Get cluster context
37-
result = subprocess.run(
38-
["kubectl", "config", "current-context"],
39-
capture_output=True,
40-
text=True,
41-
check=True,
42-
)
43-
cluster_context = result.stdout.strip()
29+
cluster_context = get_context()
4430
success(f"Connected to cluster: {cluster_context}")
4531

4632
# Run spread tests from manifest
@@ -57,7 +43,7 @@ def spread(manifest_path: str) -> None:
5743

5844
success("Spread tests completed")
5945

60-
except Exception as e:
46+
except ClusterError as e:
6147
exception("Spread tests failed", e)
6248
raise typer.Exit(code=1) from e
6349

src/kube_galaxy/pkg/components/_base.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@
1818
from kube_galaxy.pkg.arch.detector import ArchInfo
1919
from kube_galaxy.pkg.literals import Commands, Permissions, SystemPaths, Timeouts
2020
from kube_galaxy.pkg.manifest.models import ComponentConfig, InstallMethod, Manifest
21+
from kube_galaxy.pkg.utils.client import apply_manifest
2122
from kube_galaxy.pkg.utils.components import (
2223
download_file,
2324
extract_archive,
2425
format_component_pattern,
2526
install_binary,
2627
)
27-
from kube_galaxy.pkg.utils.errors import ComponentError
28+
from kube_galaxy.pkg.utils.errors import ClusterError, ComponentError
2829
from kube_galaxy.pkg.utils.logging import info
2930
from kube_galaxy.pkg.utils.shell import run
3031

@@ -243,8 +244,10 @@ def bootstrap_hook(self) -> None:
243244
raise ComponentError(
244245
f"{comp_name} manifest not downloaded. Run download hook first."
245246
)
246-
run(["kubectl", "apply", "-f", str(self.manifest_path)], check=True)
247-
info(f"Applied manifest for {comp_name}")
247+
try:
248+
apply_manifest(self.manifest_path)
249+
except ClusterError as e:
250+
raise ComponentError(f"Failed to apply manifest for {comp_name}") from e
248251

249252
pass
250253

src/kube_galaxy/pkg/components/kubeadm.py

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414

1515
from kube_galaxy.pkg.components import ClusterComponentBase, register_component
1616
from kube_galaxy.pkg.literals import Commands, SystemPaths, URLs
17+
from kube_galaxy.pkg.utils.client import (
18+
get_api_server_status,
19+
verify_connectivity,
20+
wait_for_nodes,
21+
)
1722
from kube_galaxy.pkg.utils.errors import ComponentError
1823
from kube_galaxy.pkg.utils.logging import info
1924
from kube_galaxy.pkg.utils.shell import run
@@ -184,26 +189,9 @@ def verify_hook(self) -> None:
184189
185190
Checks cluster connectivity and waits for nodes/pods to be ready.
186191
"""
187-
188-
# Check cluster info
189-
run(["kubectl", "cluster-info"], check=True)
190-
191-
# Wait for nodes to be ready
192-
run(
193-
["kubectl", "wait", "--for=condition=Ready", "nodes", "--all", "--timeout=300s"],
194-
check=True,
195-
)
196-
197-
# Wait for api-server to be ready
198-
run(
199-
[
200-
"kubectl",
201-
"get",
202-
"--raw=/readyz",
203-
"--request-timeout=300s",
204-
],
205-
check=True,
206-
)
192+
verify_connectivity()
193+
wait_for_nodes(timeout=300)
194+
get_api_server_status(timeout=300)
207195

208196
def stop_hook(self) -> None:
209197
"""

src/kube_galaxy/pkg/testing/spread.py

Lines changed: 10 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
task_path_for_component,
1919
validate_component_test_structure,
2020
)
21+
from kube_galaxy.pkg.utils.client import create_namespace, delete_namespace, verify_connectivity
2122
from kube_galaxy.pkg.utils.errors import ClusterError
2223
from kube_galaxy.pkg.utils.logging import error, info, section, success, warning
2324
from kube_galaxy.pkg.utils.shell import ShellError, run
@@ -111,9 +112,7 @@ def run_spread_tests(
111112
def _verify_test_prerequisites() -> None:
112113
"""Verify kubectl and spread are available."""
113114
try:
114-
info("Verifying cluster connectivity...")
115-
run(["kubectl", "cluster-info"], check=True, capture_output=True)
116-
success("Connected to Kubernetes cluster")
115+
verify_connectivity()
117116

118117
# Check for spread
119118
info("Verifying spread test framework...")
@@ -147,54 +146,12 @@ def _create_test_namespace(component_name: str) -> str:
147146
# Normalize component name for namespace (lowercase, hyphens only)
148147
namespace = f"kube-galaxy-test-{component_name.lower().replace('_', '-')}"
149148

150-
try:
151-
info(f" Creating test namespace: {namespace}")
152-
153-
# Apply with labels
154-
run(["kubectl", "create", "namespace", namespace], check=True)
155-
156-
# Label namespace
157-
label = "app.kubernetes.io/managed-by=kube-galaxy"
158-
run(
159-
["kubectl", "label", "namespace", namespace, label, f"component={component_name}"],
160-
check=True,
161-
)
162-
163-
success(f"Namespace created: {namespace}")
164-
return namespace
165-
166-
except ShellError as exc:
167-
raise ClusterError(f"Failed to create namespace {namespace}: {exc}") from exc
168-
169-
170-
def _cleanup_test_namespace(namespace: str, timeout: int = 60) -> None:
171-
"""
172-
Delete test namespace and wait for termination.
173-
174-
Args:
175-
namespace: Namespace to delete
176-
timeout: Maximum seconds to wait for deletion
177-
178-
Raises:
179-
ClusterError: If namespace deletion fails
180-
"""
181-
try:
182-
info(f" Cleaning up namespace: {namespace}")
183-
184-
# Delete namespace
185-
run(
186-
["kubectl", "delete", "namespace", namespace, "--timeout", f"{timeout}s"],
187-
check=True,
188-
)
189-
190-
success(f"Namespace deleted: {namespace}")
191-
192-
except ShellError as exc:
193-
# Don't fail if namespace doesn't exist
194-
if "not found" in str(exc):
195-
warning(f" Namespace {namespace} not found (may already be deleted)")
196-
else:
197-
raise ClusterError(f"Failed to delete namespace {namespace}: {exc}") from exc
149+
labels = {
150+
"app.kubernetes.io/managed-by": "kube-galaxy",
151+
"component": component_name,
152+
}
153+
create_namespace(namespace, labels)
154+
return namespace
198155

199156

200157
def _generate_orchestration_spread_yaml(
@@ -350,11 +307,7 @@ def _run_component_tests(
350307
raise ClusterError("Component validation failed")
351308

352309
# Generate orchestration spread.yaml
353-
try:
354-
component_suites = _generate_orchestration_spread_yaml(spread_components, kubeconfig)
355-
except ClusterError as exc:
356-
error(f"Failed to generate orchestration spread.yaml: {exc}")
357-
raise
310+
component_suites = _generate_orchestration_spread_yaml(spread_components, kubeconfig)
358311

359312
# Track test results
360313
test_results = []
@@ -412,7 +365,7 @@ def _run_component_tests(
412365
# Step 4: Cleanup namespace (always executed)
413366
if namespace:
414367
try:
415-
_cleanup_test_namespace(namespace)
368+
delete_namespace(namespace)
416369
except Exception as cleanup_exc:
417370
warning(f" Namespace cleanup failed: {cleanup_exc}")
418371

0 commit comments

Comments
 (0)