k8s-helper-cli 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.
- k8s_helper/__init__.py +87 -0
- k8s_helper/cli.py +526 -0
- k8s_helper/config.py +204 -0
- k8s_helper/core.py +511 -0
- k8s_helper/utils.py +301 -0
- k8s_helper_cli-0.1.0.dist-info/METADATA +491 -0
- k8s_helper_cli-0.1.0.dist-info/RECORD +11 -0
- k8s_helper_cli-0.1.0.dist-info/WHEEL +5 -0
- k8s_helper_cli-0.1.0.dist-info/entry_points.txt +2 -0
- k8s_helper_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- k8s_helper_cli-0.1.0.dist-info/top_level.txt +1 -0
k8s_helper/__init__.py
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
"""
|
2
|
+
k8s-helper: A simplified Python wrapper for common Kubernetes operations
|
3
|
+
"""
|
4
|
+
|
5
|
+
from .core import K8sClient
|
6
|
+
from .config import K8sConfig, get_config
|
7
|
+
from .utils import (
|
8
|
+
format_pod_list,
|
9
|
+
format_deployment_list,
|
10
|
+
format_service_list,
|
11
|
+
format_events,
|
12
|
+
format_age,
|
13
|
+
validate_name,
|
14
|
+
validate_namespace,
|
15
|
+
validate_image,
|
16
|
+
parse_env_vars,
|
17
|
+
parse_labels,
|
18
|
+
print_status,
|
19
|
+
create_deployment_manifest,
|
20
|
+
create_service_manifest
|
21
|
+
)
|
22
|
+
|
23
|
+
__version__ = "0.1.0"
|
24
|
+
__author__ = "Harshit Chatterjee"
|
25
|
+
__email__ = "harshitchatterjee50@gmail.com"
|
26
|
+
|
27
|
+
# Convenience functions for quick operations
|
28
|
+
def quick_deployment(name: str, image: str, replicas: int = 1, namespace: str = "default") -> bool:
|
29
|
+
"""Quickly create a deployment"""
|
30
|
+
client = K8sClient(namespace=namespace)
|
31
|
+
result = client.create_deployment(name, image, replicas)
|
32
|
+
return result is not None
|
33
|
+
|
34
|
+
def quick_service(name: str, port: int, target_port: int = None, namespace: str = "default") -> bool:
|
35
|
+
"""Quickly create a service"""
|
36
|
+
if target_port is None:
|
37
|
+
target_port = port
|
38
|
+
|
39
|
+
client = K8sClient(namespace=namespace)
|
40
|
+
result = client.create_service(name, port, target_port)
|
41
|
+
return result is not None
|
42
|
+
|
43
|
+
def quick_scale(deployment_name: str, replicas: int, namespace: str = "default") -> bool:
|
44
|
+
"""Quickly scale a deployment"""
|
45
|
+
client = K8sClient(namespace=namespace)
|
46
|
+
return client.scale_deployment(deployment_name, replicas)
|
47
|
+
|
48
|
+
def quick_logs(pod_name: str, namespace: str = "default") -> str:
|
49
|
+
"""Quickly get pod logs"""
|
50
|
+
client = K8sClient(namespace=namespace)
|
51
|
+
return client.get_logs(pod_name)
|
52
|
+
|
53
|
+
def quick_delete_deployment(name: str, namespace: str = "default") -> bool:
|
54
|
+
"""Quickly delete a deployment"""
|
55
|
+
client = K8sClient(namespace=namespace)
|
56
|
+
return client.delete_deployment(name)
|
57
|
+
|
58
|
+
def quick_delete_service(name: str, namespace: str = "default") -> bool:
|
59
|
+
"""Quickly delete a service"""
|
60
|
+
client = K8sClient(namespace=namespace)
|
61
|
+
return client.delete_service(name)
|
62
|
+
|
63
|
+
# Export main classes and functions
|
64
|
+
__all__ = [
|
65
|
+
'K8sClient',
|
66
|
+
'K8sConfig',
|
67
|
+
'get_config',
|
68
|
+
'format_pod_list',
|
69
|
+
'format_deployment_list',
|
70
|
+
'format_service_list',
|
71
|
+
'format_events',
|
72
|
+
'format_age',
|
73
|
+
'validate_name',
|
74
|
+
'validate_namespace',
|
75
|
+
'validate_image',
|
76
|
+
'parse_env_vars',
|
77
|
+
'parse_labels',
|
78
|
+
'print_status',
|
79
|
+
'create_deployment_manifest',
|
80
|
+
'create_service_manifest',
|
81
|
+
'quick_deployment',
|
82
|
+
'quick_service',
|
83
|
+
'quick_scale',
|
84
|
+
'quick_logs',
|
85
|
+
'quick_delete_deployment',
|
86
|
+
'quick_delete_service'
|
87
|
+
]
|
k8s_helper/cli.py
ADDED
@@ -0,0 +1,526 @@
|
|
1
|
+
"""
|
2
|
+
Command-line interface for k8s-helper
|
3
|
+
"""
|
4
|
+
|
5
|
+
import typer
|
6
|
+
from typing import Optional, List
|
7
|
+
from rich.console import Console
|
8
|
+
from rich.table import Table
|
9
|
+
from rich.panel import Panel
|
10
|
+
from rich.text import Text
|
11
|
+
|
12
|
+
from .core import K8sClient
|
13
|
+
from .config import get_config
|
14
|
+
from .utils import (
|
15
|
+
format_pod_list,
|
16
|
+
format_deployment_list,
|
17
|
+
format_service_list,
|
18
|
+
format_events,
|
19
|
+
format_yaml_output,
|
20
|
+
format_json_output,
|
21
|
+
validate_name,
|
22
|
+
validate_image,
|
23
|
+
parse_env_vars,
|
24
|
+
parse_labels
|
25
|
+
)
|
26
|
+
|
27
|
+
app = typer.Typer(help="k8s-helper: Simplified Kubernetes operations")
|
28
|
+
console = Console()
|
29
|
+
|
30
|
+
# Global options
|
31
|
+
namespace_option = typer.Option(None, "--namespace", "-n", help="Kubernetes namespace")
|
32
|
+
output_option = typer.Option("table", "--output", "-o", help="Output format: table, yaml, json")
|
33
|
+
|
34
|
+
|
35
|
+
@app.command()
|
36
|
+
def config(
|
37
|
+
namespace: Optional[str] = typer.Option(None, help="Set default namespace"),
|
38
|
+
output_format: Optional[str] = typer.Option(None, help="Set output format"),
|
39
|
+
timeout: Optional[int] = typer.Option(None, help="Set default timeout"),
|
40
|
+
verbose: Optional[bool] = typer.Option(None, help="Enable verbose output"),
|
41
|
+
show: bool = typer.Option(False, "--show", help="Show current configuration")
|
42
|
+
):
|
43
|
+
"""Configure k8s-helper settings"""
|
44
|
+
config_obj = get_config()
|
45
|
+
|
46
|
+
if show:
|
47
|
+
console.print(Panel(format_yaml_output(config_obj.to_dict()), title="Current Configuration"))
|
48
|
+
return
|
49
|
+
|
50
|
+
if namespace:
|
51
|
+
config_obj.set_namespace(namespace)
|
52
|
+
console.print(f"โ
Default namespace set to: {namespace}")
|
53
|
+
|
54
|
+
if output_format:
|
55
|
+
try:
|
56
|
+
config_obj.set_output_format(output_format)
|
57
|
+
console.print(f"โ
Output format set to: {output_format}")
|
58
|
+
except ValueError as e:
|
59
|
+
console.print(f"โ {e}")
|
60
|
+
return
|
61
|
+
|
62
|
+
if timeout:
|
63
|
+
config_obj.set_timeout(timeout)
|
64
|
+
console.print(f"โ
Timeout set to: {timeout} seconds")
|
65
|
+
|
66
|
+
if verbose is not None:
|
67
|
+
config_obj.set_verbose(verbose)
|
68
|
+
console.print(f"โ
Verbose mode: {'enabled' if verbose else 'disabled'}")
|
69
|
+
|
70
|
+
if any([namespace, output_format, timeout, verbose is not None]):
|
71
|
+
config_obj.save_config()
|
72
|
+
console.print("โ
Configuration saved")
|
73
|
+
|
74
|
+
|
75
|
+
@app.command()
|
76
|
+
def create_deployment(
|
77
|
+
name: str = typer.Argument(..., help="Deployment name"),
|
78
|
+
image: str = typer.Argument(..., help="Container image"),
|
79
|
+
replicas: int = typer.Option(1, "--replicas", "-r", help="Number of replicas"),
|
80
|
+
port: int = typer.Option(80, "--port", "-p", help="Container port"),
|
81
|
+
env: Optional[str] = typer.Option(None, "--env", "-e", help="Environment variables (KEY1=value1,KEY2=value2)"),
|
82
|
+
labels: Optional[str] = typer.Option(None, "--labels", "-l", help="Labels (key1=value1,key2=value2)"),
|
83
|
+
namespace: Optional[str] = namespace_option,
|
84
|
+
wait: bool = typer.Option(False, "--wait", help="Wait for deployment to be ready")
|
85
|
+
):
|
86
|
+
"""Create a new deployment"""
|
87
|
+
if not validate_name(name):
|
88
|
+
console.print(f"โ Invalid deployment name: {name}")
|
89
|
+
return
|
90
|
+
|
91
|
+
if not validate_image(image):
|
92
|
+
console.print(f"โ Invalid image name: {image}")
|
93
|
+
return
|
94
|
+
|
95
|
+
# Parse environment variables and labels
|
96
|
+
env_vars = parse_env_vars(env) if env else None
|
97
|
+
label_dict = parse_labels(labels) if labels else None
|
98
|
+
|
99
|
+
# Get client
|
100
|
+
ns = namespace or get_config().get_namespace()
|
101
|
+
client = K8sClient(namespace=ns)
|
102
|
+
|
103
|
+
# Create deployment
|
104
|
+
with console.status(f"Creating deployment {name}..."):
|
105
|
+
result = client.create_deployment(
|
106
|
+
name=name,
|
107
|
+
image=image,
|
108
|
+
replicas=replicas,
|
109
|
+
container_port=port,
|
110
|
+
env_vars=env_vars,
|
111
|
+
labels=label_dict
|
112
|
+
)
|
113
|
+
|
114
|
+
if result:
|
115
|
+
console.print(f"โ
Deployment {name} created successfully")
|
116
|
+
|
117
|
+
if wait:
|
118
|
+
with console.status(f"Waiting for deployment {name} to be ready..."):
|
119
|
+
if client.wait_for_deployment_ready(name):
|
120
|
+
console.print(f"โ
Deployment {name} is ready")
|
121
|
+
else:
|
122
|
+
console.print(f"โ Deployment {name} failed to become ready")
|
123
|
+
else:
|
124
|
+
console.print(f"โ Failed to create deployment {name}")
|
125
|
+
|
126
|
+
|
127
|
+
@app.command()
|
128
|
+
def delete_deployment(
|
129
|
+
name: str = typer.Argument(..., help="Deployment name"),
|
130
|
+
namespace: Optional[str] = namespace_option
|
131
|
+
):
|
132
|
+
"""Delete a deployment"""
|
133
|
+
ns = namespace or get_config().get_namespace()
|
134
|
+
client = K8sClient(namespace=ns)
|
135
|
+
|
136
|
+
if typer.confirm(f"Are you sure you want to delete deployment {name}?"):
|
137
|
+
with console.status(f"Deleting deployment {name}..."):
|
138
|
+
if client.delete_deployment(name):
|
139
|
+
console.print(f"โ
Deployment {name} deleted successfully")
|
140
|
+
else:
|
141
|
+
console.print(f"โ Failed to delete deployment {name}")
|
142
|
+
|
143
|
+
|
144
|
+
@app.command()
|
145
|
+
def scale_deployment(
|
146
|
+
name: str = typer.Argument(..., help="Deployment name"),
|
147
|
+
replicas: int = typer.Argument(..., help="Number of replicas"),
|
148
|
+
namespace: Optional[str] = namespace_option
|
149
|
+
):
|
150
|
+
"""Scale a deployment"""
|
151
|
+
ns = namespace or get_config().get_namespace()
|
152
|
+
client = K8sClient(namespace=ns)
|
153
|
+
|
154
|
+
with console.status(f"Scaling deployment {name} to {replicas} replicas..."):
|
155
|
+
if client.scale_deployment(name, replicas):
|
156
|
+
console.print(f"โ
Deployment {name} scaled to {replicas} replicas")
|
157
|
+
else:
|
158
|
+
console.print(f"โ Failed to scale deployment {name}")
|
159
|
+
|
160
|
+
|
161
|
+
@app.command()
|
162
|
+
def list_deployments(
|
163
|
+
namespace: Optional[str] = namespace_option,
|
164
|
+
output: str = output_option
|
165
|
+
):
|
166
|
+
"""List deployments"""
|
167
|
+
ns = namespace or get_config().get_namespace()
|
168
|
+
client = K8sClient(namespace=ns)
|
169
|
+
|
170
|
+
deployments = client.list_deployments()
|
171
|
+
|
172
|
+
if output == "table":
|
173
|
+
console.print(format_deployment_list(deployments))
|
174
|
+
elif output == "yaml":
|
175
|
+
console.print(format_yaml_output(deployments))
|
176
|
+
elif output == "json":
|
177
|
+
console.print(format_json_output(deployments))
|
178
|
+
|
179
|
+
|
180
|
+
@app.command()
|
181
|
+
def create_pod(
|
182
|
+
name: str = typer.Argument(..., help="Pod name"),
|
183
|
+
image: str = typer.Argument(..., help="Container image"),
|
184
|
+
port: int = typer.Option(80, "--port", "-p", help="Container port"),
|
185
|
+
env: Optional[str] = typer.Option(None, "--env", "-e", help="Environment variables"),
|
186
|
+
labels: Optional[str] = typer.Option(None, "--labels", "-l", help="Labels"),
|
187
|
+
namespace: Optional[str] = namespace_option
|
188
|
+
):
|
189
|
+
"""Create a new pod"""
|
190
|
+
if not validate_name(name):
|
191
|
+
console.print(f"โ Invalid pod name: {name}")
|
192
|
+
return
|
193
|
+
|
194
|
+
if not validate_image(image):
|
195
|
+
console.print(f"โ Invalid image name: {image}")
|
196
|
+
return
|
197
|
+
|
198
|
+
env_vars = parse_env_vars(env) if env else None
|
199
|
+
label_dict = parse_labels(labels) if labels else None
|
200
|
+
|
201
|
+
ns = namespace or get_config().get_namespace()
|
202
|
+
client = K8sClient(namespace=ns)
|
203
|
+
|
204
|
+
with console.status(f"Creating pod {name}..."):
|
205
|
+
result = client.create_pod(
|
206
|
+
name=name,
|
207
|
+
image=image,
|
208
|
+
container_port=port,
|
209
|
+
env_vars=env_vars,
|
210
|
+
labels=label_dict
|
211
|
+
)
|
212
|
+
|
213
|
+
if result:
|
214
|
+
console.print(f"โ
Pod {name} created successfully")
|
215
|
+
else:
|
216
|
+
console.print(f"โ Failed to create pod {name}")
|
217
|
+
|
218
|
+
|
219
|
+
@app.command()
|
220
|
+
def delete_pod(
|
221
|
+
name: str = typer.Argument(..., help="Pod name"),
|
222
|
+
namespace: Optional[str] = namespace_option
|
223
|
+
):
|
224
|
+
"""Delete a pod"""
|
225
|
+
ns = namespace or get_config().get_namespace()
|
226
|
+
client = K8sClient(namespace=ns)
|
227
|
+
|
228
|
+
if typer.confirm(f"Are you sure you want to delete pod {name}?"):
|
229
|
+
with console.status(f"Deleting pod {name}..."):
|
230
|
+
if client.delete_pod(name):
|
231
|
+
console.print(f"โ
Pod {name} deleted successfully")
|
232
|
+
else:
|
233
|
+
console.print(f"โ Failed to delete pod {name}")
|
234
|
+
|
235
|
+
|
236
|
+
@app.command()
|
237
|
+
def list_pods(
|
238
|
+
namespace: Optional[str] = namespace_option,
|
239
|
+
output: str = output_option
|
240
|
+
):
|
241
|
+
"""List pods"""
|
242
|
+
ns = namespace or get_config().get_namespace()
|
243
|
+
client = K8sClient(namespace=ns)
|
244
|
+
|
245
|
+
pods = client.list_pods()
|
246
|
+
|
247
|
+
if output == "table":
|
248
|
+
console.print(format_pod_list(pods))
|
249
|
+
elif output == "yaml":
|
250
|
+
console.print(format_yaml_output(pods))
|
251
|
+
elif output == "json":
|
252
|
+
console.print(format_json_output(pods))
|
253
|
+
|
254
|
+
|
255
|
+
@app.command()
|
256
|
+
def create_service(
|
257
|
+
name: str = typer.Argument(..., help="Service name"),
|
258
|
+
port: int = typer.Argument(..., help="Service port"),
|
259
|
+
target_port: Optional[int] = typer.Option(None, help="Target port (defaults to port)"),
|
260
|
+
service_type: str = typer.Option("ClusterIP", help="Service type"),
|
261
|
+
selector: Optional[str] = typer.Option(None, help="Selector labels"),
|
262
|
+
namespace: Optional[str] = namespace_option
|
263
|
+
):
|
264
|
+
"""Create a new service"""
|
265
|
+
if not validate_name(name):
|
266
|
+
console.print(f"โ Invalid service name: {name}")
|
267
|
+
return
|
268
|
+
|
269
|
+
if target_port is None:
|
270
|
+
target_port = port
|
271
|
+
|
272
|
+
selector_dict = parse_labels(selector) if selector else None
|
273
|
+
|
274
|
+
ns = namespace or get_config().get_namespace()
|
275
|
+
client = K8sClient(namespace=ns)
|
276
|
+
|
277
|
+
with console.status(f"Creating service {name}..."):
|
278
|
+
result = client.create_service(
|
279
|
+
name=name,
|
280
|
+
port=port,
|
281
|
+
target_port=target_port,
|
282
|
+
service_type=service_type,
|
283
|
+
selector=selector_dict
|
284
|
+
)
|
285
|
+
|
286
|
+
if result:
|
287
|
+
console.print(f"โ
Service {name} created successfully")
|
288
|
+
else:
|
289
|
+
console.print(f"โ Failed to create service {name}")
|
290
|
+
|
291
|
+
|
292
|
+
@app.command()
|
293
|
+
def delete_service(
|
294
|
+
name: str = typer.Argument(..., help="Service name"),
|
295
|
+
namespace: Optional[str] = namespace_option
|
296
|
+
):
|
297
|
+
"""Delete a service"""
|
298
|
+
ns = namespace or get_config().get_namespace()
|
299
|
+
client = K8sClient(namespace=ns)
|
300
|
+
|
301
|
+
if typer.confirm(f"Are you sure you want to delete service {name}?"):
|
302
|
+
with console.status(f"Deleting service {name}..."):
|
303
|
+
if client.delete_service(name):
|
304
|
+
console.print(f"โ
Service {name} deleted successfully")
|
305
|
+
else:
|
306
|
+
console.print(f"โ Failed to delete service {name}")
|
307
|
+
|
308
|
+
|
309
|
+
@app.command()
|
310
|
+
def list_services(
|
311
|
+
namespace: Optional[str] = namespace_option,
|
312
|
+
output: str = output_option
|
313
|
+
):
|
314
|
+
"""List services"""
|
315
|
+
ns = namespace or get_config().get_namespace()
|
316
|
+
client = K8sClient(namespace=ns)
|
317
|
+
|
318
|
+
services = client.list_services()
|
319
|
+
|
320
|
+
if output == "table":
|
321
|
+
console.print(format_service_list(services))
|
322
|
+
elif output == "yaml":
|
323
|
+
console.print(format_yaml_output(services))
|
324
|
+
elif output == "json":
|
325
|
+
console.print(format_json_output(services))
|
326
|
+
|
327
|
+
|
328
|
+
@app.command()
|
329
|
+
def logs(
|
330
|
+
pod_name: str = typer.Argument(..., help="Pod name"),
|
331
|
+
container: Optional[str] = typer.Option(None, help="Container name"),
|
332
|
+
tail: Optional[int] = typer.Option(None, help="Number of lines to tail"),
|
333
|
+
namespace: Optional[str] = namespace_option
|
334
|
+
):
|
335
|
+
"""Get pod logs"""
|
336
|
+
ns = namespace or get_config().get_namespace()
|
337
|
+
client = K8sClient(namespace=ns)
|
338
|
+
|
339
|
+
logs = client.get_logs(pod_name, container_name=container, tail_lines=tail)
|
340
|
+
if logs:
|
341
|
+
console.print(logs)
|
342
|
+
else:
|
343
|
+
console.print(f"โ Failed to get logs for pod {pod_name}")
|
344
|
+
|
345
|
+
|
346
|
+
@app.command()
|
347
|
+
def events(
|
348
|
+
resource: Optional[str] = typer.Option(None, help="Resource name to filter events"),
|
349
|
+
namespace: Optional[str] = namespace_option,
|
350
|
+
output: str = output_option
|
351
|
+
):
|
352
|
+
"""Get events"""
|
353
|
+
ns = namespace or get_config().get_namespace()
|
354
|
+
client = K8sClient(namespace=ns)
|
355
|
+
|
356
|
+
events = client.get_events(resource_name=resource)
|
357
|
+
|
358
|
+
if output == "table":
|
359
|
+
console.print(format_events(events))
|
360
|
+
elif output == "yaml":
|
361
|
+
console.print(format_yaml_output(events))
|
362
|
+
elif output == "json":
|
363
|
+
console.print(format_json_output(events))
|
364
|
+
|
365
|
+
|
366
|
+
@app.command()
|
367
|
+
def describe(
|
368
|
+
resource_type: str = typer.Argument(..., help="Resource type: pod, deployment, service"),
|
369
|
+
name: str = typer.Argument(..., help="Resource name"),
|
370
|
+
namespace: Optional[str] = namespace_option,
|
371
|
+
output: str = output_option
|
372
|
+
):
|
373
|
+
"""Describe a resource"""
|
374
|
+
ns = namespace or get_config().get_namespace()
|
375
|
+
client = K8sClient(namespace=ns)
|
376
|
+
|
377
|
+
if resource_type.lower() == "pod":
|
378
|
+
info = client.describe_pod(name)
|
379
|
+
elif resource_type.lower() == "deployment":
|
380
|
+
info = client.describe_deployment(name)
|
381
|
+
elif resource_type.lower() == "service":
|
382
|
+
info = client.describe_service(name)
|
383
|
+
else:
|
384
|
+
console.print(f"โ Unsupported resource type: {resource_type}")
|
385
|
+
return
|
386
|
+
|
387
|
+
if info:
|
388
|
+
if output == "yaml":
|
389
|
+
console.print(format_yaml_output(info))
|
390
|
+
elif output == "json":
|
391
|
+
console.print(format_json_output(info))
|
392
|
+
else:
|
393
|
+
console.print(format_yaml_output(info)) # Default to YAML for describe
|
394
|
+
else:
|
395
|
+
console.print(f"โ Failed to describe {resource_type} {name}")
|
396
|
+
|
397
|
+
|
398
|
+
@app.command()
|
399
|
+
def status(
|
400
|
+
namespace: Optional[str] = namespace_option
|
401
|
+
):
|
402
|
+
"""Show namespace status"""
|
403
|
+
ns = namespace or get_config().get_namespace()
|
404
|
+
client = K8sClient(namespace=ns)
|
405
|
+
|
406
|
+
console.print(f"\n[bold]Namespace: {ns}[/bold]")
|
407
|
+
|
408
|
+
# Get resource counts
|
409
|
+
resources = client.get_namespace_resources()
|
410
|
+
|
411
|
+
table = Table(title="Resource Summary")
|
412
|
+
table.add_column("Resource", style="cyan")
|
413
|
+
table.add_column("Count", style="magenta")
|
414
|
+
|
415
|
+
for resource, count in resources.items():
|
416
|
+
table.add_row(resource.capitalize(), str(count))
|
417
|
+
|
418
|
+
console.print(table)
|
419
|
+
|
420
|
+
# Show recent events
|
421
|
+
events = client.get_events()
|
422
|
+
if events:
|
423
|
+
console.print(f"\n[bold]Recent Events (last 5):[/bold]")
|
424
|
+
recent_events = events[:5]
|
425
|
+
for event in recent_events:
|
426
|
+
event_type = event['type']
|
427
|
+
color = "green" if event_type == "Normal" else "red"
|
428
|
+
console.print(f"[{color}]{event['type']}[/{color}] {event['reason']}: {event['message']}")
|
429
|
+
|
430
|
+
|
431
|
+
@app.command()
|
432
|
+
def apply(
|
433
|
+
name: str = typer.Argument(..., help="Application name"),
|
434
|
+
image: str = typer.Argument(..., help="Container image"),
|
435
|
+
replicas: int = typer.Option(1, "--replicas", "-r", help="Number of replicas"),
|
436
|
+
port: int = typer.Option(80, "--port", "-p", help="Container port"),
|
437
|
+
service_type: str = typer.Option("ClusterIP", help="Service type"),
|
438
|
+
env: Optional[str] = typer.Option(None, "--env", "-e", help="Environment variables"),
|
439
|
+
labels: Optional[str] = typer.Option(None, "--labels", "-l", help="Labels"),
|
440
|
+
namespace: Optional[str] = namespace_option,
|
441
|
+
wait: bool = typer.Option(True, "--wait/--no-wait", help="Wait for deployment to be ready")
|
442
|
+
):
|
443
|
+
"""Deploy an application (deployment + service)"""
|
444
|
+
if not validate_name(name):
|
445
|
+
console.print(f"โ Invalid application name: {name}")
|
446
|
+
return
|
447
|
+
|
448
|
+
if not validate_image(image):
|
449
|
+
console.print(f"โ Invalid image name: {image}")
|
450
|
+
return
|
451
|
+
|
452
|
+
env_vars = parse_env_vars(env) if env else None
|
453
|
+
label_dict = parse_labels(labels) if labels else None
|
454
|
+
|
455
|
+
ns = namespace or get_config().get_namespace()
|
456
|
+
client = K8sClient(namespace=ns)
|
457
|
+
|
458
|
+
console.print(f"๐ Deploying application: {name}")
|
459
|
+
|
460
|
+
# Create deployment
|
461
|
+
with console.status(f"Creating deployment {name}..."):
|
462
|
+
deployment_result = client.create_deployment(
|
463
|
+
name=name,
|
464
|
+
image=image,
|
465
|
+
replicas=replicas,
|
466
|
+
container_port=port,
|
467
|
+
env_vars=env_vars,
|
468
|
+
labels=label_dict
|
469
|
+
)
|
470
|
+
|
471
|
+
if not deployment_result:
|
472
|
+
console.print(f"โ Failed to create deployment {name}")
|
473
|
+
return
|
474
|
+
|
475
|
+
# Create service
|
476
|
+
with console.status(f"Creating service {name}-service..."):
|
477
|
+
service_result = client.create_service(
|
478
|
+
name=f"{name}-service",
|
479
|
+
port=port,
|
480
|
+
target_port=port,
|
481
|
+
service_type=service_type,
|
482
|
+
selector=label_dict or {"app": name}
|
483
|
+
)
|
484
|
+
|
485
|
+
if not service_result:
|
486
|
+
console.print(f"โ Failed to create service {name}-service")
|
487
|
+
return
|
488
|
+
|
489
|
+
console.print(f"โ
Application {name} deployed successfully")
|
490
|
+
|
491
|
+
if wait:
|
492
|
+
with console.status(f"Waiting for deployment {name} to be ready..."):
|
493
|
+
if client.wait_for_deployment_ready(name):
|
494
|
+
console.print(f"โ
Application {name} is ready")
|
495
|
+
else:
|
496
|
+
console.print(f"โ Application {name} failed to become ready")
|
497
|
+
|
498
|
+
|
499
|
+
@app.command()
|
500
|
+
def cleanup(
|
501
|
+
name: str = typer.Argument(..., help="Application name"),
|
502
|
+
namespace: Optional[str] = namespace_option
|
503
|
+
):
|
504
|
+
"""Clean up an application (delete deployment + service)"""
|
505
|
+
ns = namespace or get_config().get_namespace()
|
506
|
+
client = K8sClient(namespace=ns)
|
507
|
+
|
508
|
+
if typer.confirm(f"Are you sure you want to delete application {name} and its service?"):
|
509
|
+
console.print(f"๐งน Cleaning up application: {name}")
|
510
|
+
|
511
|
+
# Delete deployment
|
512
|
+
with console.status(f"Deleting deployment {name}..."):
|
513
|
+
deployment_deleted = client.delete_deployment(name)
|
514
|
+
|
515
|
+
# Delete service
|
516
|
+
with console.status(f"Deleting service {name}-service..."):
|
517
|
+
service_deleted = client.delete_service(f"{name}-service")
|
518
|
+
|
519
|
+
if deployment_deleted and service_deleted:
|
520
|
+
console.print(f"โ
Application {name} cleaned up successfully")
|
521
|
+
else:
|
522
|
+
console.print(f"โ ๏ธ Partial cleanup completed for application {name}")
|
523
|
+
|
524
|
+
|
525
|
+
if __name__ == "__main__":
|
526
|
+
app()
|