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 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()