k8s-helper-cli 0.2.7__py3-none-any.whl → 0.3.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 CHANGED
@@ -20,7 +20,7 @@ from .utils import (
20
20
  create_service_manifest
21
21
  )
22
22
 
23
- __version__ = "0.2.7"
23
+ __version__ = "0.3.0"
24
24
  __author__ = "Harshit Chatterjee"
25
25
  __email__ = "harshitchatterjee50@gmail.com"
26
26
 
k8s_helper/cli.py CHANGED
@@ -175,6 +175,117 @@ def scale_deployment(
175
175
  console.print(f"❌ Failed to scale deployment {name}")
176
176
 
177
177
 
178
+ @app.command()
179
+ def rolling_update(
180
+ name: str = typer.Argument(..., help="Deployment name"),
181
+ image: Optional[str] = typer.Option(None, "--image", "-i", help="New container image"),
182
+ replicas: Optional[int] = typer.Option(None, "--replicas", "-r", help="New number of replicas"),
183
+ port: Optional[int] = typer.Option(None, "--port", "-p", help="New container port"),
184
+ env: Optional[str] = typer.Option(None, "--env", "-e", help="New environment variables (KEY1=value1,KEY2=value2)"),
185
+ labels: Optional[str] = typer.Option(None, "--labels", "-l", help="New labels (key1=value1,key2=value2)"),
186
+ namespace: Optional[str] = namespace_option,
187
+ wait: bool = typer.Option(True, "--wait/--no-wait", help="Wait for rollout to complete"),
188
+ timeout: int = typer.Option(300, "--timeout", "-t", help="Timeout in seconds for rollout completion"),
189
+ show_status: bool = typer.Option(False, "--show-status", "-s", help="Show rollout status during update")
190
+ ):
191
+ """Perform a rolling update on a deployment"""
192
+ if not validate_name(name):
193
+ console.print(f"❌ Invalid deployment name: {name}")
194
+ return
195
+
196
+ # Validate that at least one parameter is provided
197
+ if not any([image, replicas is not None, port, env, labels]):
198
+ console.print("❌ At least one update parameter must be provided")
199
+ console.print(" Use --image, --replicas, --port, --env, or --labels")
200
+ return
201
+
202
+ # Validate image if provided
203
+ if image and not validate_image(image):
204
+ console.print(f"❌ Invalid image name: {image}")
205
+ return
206
+
207
+ # Parse environment variables and labels
208
+ env_vars = parse_env_vars(env) if env else None
209
+ label_dict = parse_labels(labels) if labels else None
210
+
211
+ ns = namespace or get_config().get_namespace()
212
+ client = K8sClient(namespace=ns)
213
+
214
+ # Show what will be updated
215
+ console.print(f"🔄 Performing rolling update on deployment: {name}")
216
+ console.print(f"📍 Namespace: {ns}")
217
+
218
+ updates = []
219
+ if image:
220
+ updates.append(f"🖼️ Image: {image}")
221
+ if replicas is not None:
222
+ updates.append(f"📊 Replicas: {replicas}")
223
+ if port:
224
+ updates.append(f"🚪 Port: {port}")
225
+ if env_vars:
226
+ updates.append(f"🔧 Environment variables: {list(env_vars.keys())}")
227
+ if label_dict:
228
+ updates.append(f"🏷️ Labels: {list(label_dict.keys())}")
229
+
230
+ console.print("📋 Updates to apply:")
231
+ for update in updates:
232
+ console.print(f" • {update}")
233
+
234
+ # Perform the rolling update
235
+ with console.status(f"Applying rolling update to {name}..."):
236
+ success = client.rolling_update_deployment(
237
+ name=name,
238
+ image=image,
239
+ replicas=replicas,
240
+ env_vars=env_vars,
241
+ labels=label_dict,
242
+ container_port=port
243
+ )
244
+
245
+ if not success:
246
+ console.print(f"❌ Failed to start rolling update for deployment {name}")
247
+ return
248
+
249
+ console.print(f"✅ Rolling update initiated for deployment {name}")
250
+
251
+ if wait:
252
+ console.print(f"⏳ Waiting for rollout to complete (timeout: {timeout}s)...")
253
+
254
+ if show_status:
255
+ # Show rolling status updates
256
+ start_time = time.time()
257
+ while time.time() - start_time < timeout:
258
+ status = client.get_rollout_status(name)
259
+ if status:
260
+ console.print(f"📊 Status: {status['ready_replicas']}/{status['desired_replicas']} ready, "
261
+ f"{status['updated_replicas']} updated, {status['available_replicas']} available")
262
+
263
+ # Check if rollout is complete
264
+ if (status['ready_replicas'] == status['desired_replicas'] and
265
+ status['updated_replicas'] == status['desired_replicas'] and
266
+ status['available_replicas'] == status['desired_replicas']):
267
+ break
268
+
269
+ time.sleep(3)
270
+
271
+ # Wait for rollout completion
272
+ if client.wait_for_rollout_complete(name, timeout):
273
+ console.print("✅ Rolling update completed successfully!")
274
+
275
+ # Show final status
276
+ final_status = client.get_rollout_status(name)
277
+ if final_status:
278
+ console.print(f"📊 Final status: {final_status['ready_replicas']}/{final_status['desired_replicas']} replicas ready")
279
+
280
+ console.print(f"🎉 Deployment {name} is now running with the updated configuration")
281
+ else:
282
+ console.print(f"❌ Rolling update timed out after {timeout} seconds")
283
+ console.print("💡 Check rollout status with: kubectl rollout status deployment/{name}")
284
+ else:
285
+ console.print(f"💡 Monitor rollout progress with: kubectl rollout status deployment/{name}")
286
+ console.print(f"💡 Or use: k8s-helper rolling-update {name} --show-status --wait")
287
+
288
+
178
289
  @app.command()
179
290
  def list_deployments(
180
291
  namespace: Optional[str] = namespace_option,
k8s_helper/core.py CHANGED
@@ -834,6 +834,150 @@ class K8sClient:
834
834
  print(f"❌ Error scaling deployment '{name}': {e}")
835
835
  return False
836
836
 
837
+ def rolling_update_deployment(self, name: str, image: str = None,
838
+ replicas: int = None, env_vars: Optional[Dict[str, str]] = None,
839
+ labels: Optional[Dict[str, str]] = None,
840
+ container_port: int = None) -> bool:
841
+ """Perform a rolling update on a deployment
842
+
843
+ Args:
844
+ name: Deployment name
845
+ image: New container image (optional)
846
+ replicas: New replica count (optional)
847
+ env_vars: New environment variables (optional)
848
+ labels: New labels (optional)
849
+ container_port: New container port (optional)
850
+
851
+ Returns:
852
+ True if successful, False otherwise
853
+ """
854
+ try:
855
+ # Get current deployment
856
+ deployment = self.apps_v1.read_namespaced_deployment(
857
+ name=name,
858
+ namespace=self.namespace
859
+ )
860
+
861
+ # Update image if provided
862
+ if image:
863
+ for container in deployment.spec.template.spec.containers:
864
+ if container.name == name: # Update main container
865
+ container.image = image
866
+ break
867
+
868
+ # Update replicas if provided
869
+ if replicas is not None:
870
+ deployment.spec.replicas = replicas
871
+
872
+ # Update environment variables if provided
873
+ if env_vars:
874
+ for container in deployment.spec.template.spec.containers:
875
+ if container.name == name: # Update main container
876
+ env = [client.V1EnvVar(name=k, value=v) for k, v in env_vars.items()]
877
+ container.env = env
878
+ break
879
+
880
+ # Update labels if provided
881
+ if labels:
882
+ if deployment.spec.template.metadata.labels:
883
+ deployment.spec.template.metadata.labels.update(labels)
884
+ else:
885
+ deployment.spec.template.metadata.labels = labels
886
+
887
+ # Also update selector labels if they match
888
+ if deployment.spec.selector.match_labels:
889
+ deployment.spec.selector.match_labels.update(labels)
890
+
891
+ # Update container port if provided
892
+ if container_port:
893
+ for container in deployment.spec.template.spec.containers:
894
+ if container.name == name: # Update main container
895
+ if container.ports:
896
+ container.ports[0].container_port = container_port
897
+ else:
898
+ container.ports = [client.V1ContainerPort(container_port=container_port)]
899
+ break
900
+
901
+ # Apply the rolling update
902
+ self.apps_v1.patch_namespaced_deployment(
903
+ name=name,
904
+ namespace=self.namespace,
905
+ body=deployment
906
+ )
907
+
908
+ return True
909
+ except ApiException as e:
910
+ print(f"❌ Error performing rolling update on deployment '{name}': {e}")
911
+ return False
912
+
913
+ def wait_for_rollout_complete(self, name: str, timeout: int = 300) -> bool:
914
+ """Wait for a deployment rollout to complete
915
+
916
+ Args:
917
+ name: Deployment name
918
+ timeout: Timeout in seconds
919
+
920
+ Returns:
921
+ True if rollout completed successfully, False otherwise
922
+ """
923
+ try:
924
+ start_time = time.time()
925
+ while time.time() - start_time < timeout:
926
+ deployment = self.apps_v1.read_namespaced_deployment(
927
+ name=name,
928
+ namespace=self.namespace
929
+ )
930
+
931
+ # Check if rollout is complete
932
+ if (deployment.status.ready_replicas == deployment.spec.replicas and
933
+ deployment.status.updated_replicas == deployment.spec.replicas and
934
+ deployment.status.available_replicas == deployment.spec.replicas):
935
+ return True
936
+
937
+ time.sleep(2)
938
+
939
+ return False
940
+ except ApiException as e:
941
+ print(f"❌ Error waiting for rollout: {e}")
942
+ return False
943
+
944
+ def get_rollout_status(self, name: str) -> Dict[str, Any]:
945
+ """Get deployment rollout status
946
+
947
+ Args:
948
+ name: Deployment name
949
+
950
+ Returns:
951
+ Dictionary with rollout status information
952
+ """
953
+ try:
954
+ deployment = self.apps_v1.read_namespaced_deployment(
955
+ name=name,
956
+ namespace=self.namespace
957
+ )
958
+
959
+ return {
960
+ 'name': deployment.metadata.name,
961
+ 'desired_replicas': deployment.spec.replicas,
962
+ 'current_replicas': deployment.status.replicas or 0,
963
+ 'updated_replicas': deployment.status.updated_replicas or 0,
964
+ 'ready_replicas': deployment.status.ready_replicas or 0,
965
+ 'available_replicas': deployment.status.available_replicas or 0,
966
+ 'unavailable_replicas': deployment.status.unavailable_replicas or 0,
967
+ 'conditions': [
968
+ {
969
+ 'type': condition.type,
970
+ 'status': condition.status,
971
+ 'reason': condition.reason,
972
+ 'message': condition.message,
973
+ 'last_update_time': condition.last_update_time
974
+ } for condition in (deployment.status.conditions or [])
975
+ ]
976
+ }
977
+ except ApiException as e:
978
+ print(f"❌ Error getting rollout status: {e}")
979
+ return {}
980
+
837
981
  def list_deployments(self) -> List[Dict[str, Any]]:
838
982
  """List all deployments in the namespace"""
839
983
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: k8s-helper-cli
3
- Version: 0.2.7
3
+ Version: 0.3.0
4
4
  Summary: A simplified Python wrapper for common Kubernetes operations
5
5
  Author-email: Harshit Chatterjee <harshitchatterjee50@gmail.com>
6
6
  License-Expression: MIT
@@ -573,6 +573,15 @@ k8s-helper create-deployment my-app nginx:latest --replicas 3 --namespace my-nam
573
573
  # Scale a deployment
574
574
  k8s-helper scale-deployment my-app --replicas 5 --namespace my-namespace
575
575
 
576
+ # Perform a rolling update
577
+ k8s-helper rolling-update my-app --image nginx:1.21 --namespace my-namespace
578
+
579
+ # Rolling update with multiple changes
580
+ k8s-helper rolling-update my-app --image nginx:1.21 --replicas 3 --env "ENV=prod" --namespace my-namespace
581
+
582
+ # Rolling update with status monitoring
583
+ k8s-helper rolling-update my-app --image nginx:1.21 --show-status --namespace my-namespace
584
+
576
585
  # Delete a deployment
577
586
  k8s-helper delete-deployment my-app --namespace my-namespace
578
587
 
@@ -0,0 +1,11 @@
1
+ k8s_helper/__init__.py,sha256=DZK903fkZNiY8hFaPHgeufiLxUbdwKXGAMCOixqK_lU,2666
2
+ k8s_helper/cli.py,sha256=sOEXL_JMyIyHzOTy0GTg2Qzqs7mUhRSBz6_d8nM9HCg,55992
3
+ k8s_helper/config.py,sha256=P7YdfyvCHprrNs2J9DRb3RrClylfTTh5hfTtDzLug0A,6867
4
+ k8s_helper/core.py,sha256=-KA0GZeWWm0VDKX-Q3POr6H7kpM8swTUSDC71z-EniM,70406
5
+ k8s_helper/utils.py,sha256=wYgTd5ktyuI-EiVcfW7FrxA7MzXY5odrEKQgmMVdueY,9496
6
+ k8s_helper_cli-0.3.0.dist-info/licenses/LICENSE,sha256=tXPvVl3gLVc6e0qCEoLH9KjeA7z4JVL78UybpvGtBCw,1096
7
+ k8s_helper_cli-0.3.0.dist-info/METADATA,sha256=Ln5tPM4HkYtD7Z44XuY0a_BKTABCdm6vhAMNU3-lge0,27349
8
+ k8s_helper_cli-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
+ k8s_helper_cli-0.3.0.dist-info/entry_points.txt,sha256=IoCMWUZ6mn90LwzQzEy5YkWOwvogDdZ6ycqUWAzCFTQ,50
10
+ k8s_helper_cli-0.3.0.dist-info/top_level.txt,sha256=x9A1jflyer-z2cFnkqk5B42juoH2q0fy5hkT9upsTG8,11
11
+ k8s_helper_cli-0.3.0.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- k8s_helper/__init__.py,sha256=o14DQdASXvBo5Yni05BQwEh3XiIbRYvENC6P8tQ6f6U,2666
2
- k8s_helper/cli.py,sha256=Ol6DO2zega-f4kaTL3hH7vB9oSy5heUAMkS1DkVkIp0,50867
3
- k8s_helper/config.py,sha256=P7YdfyvCHprrNs2J9DRb3RrClylfTTh5hfTtDzLug0A,6867
4
- k8s_helper/core.py,sha256=zdw582TNyl-z2fDZ7d9vooTiafwLh2rAEHey9HoD_fI,64329
5
- k8s_helper/utils.py,sha256=wYgTd5ktyuI-EiVcfW7FrxA7MzXY5odrEKQgmMVdueY,9496
6
- k8s_helper_cli-0.2.7.dist-info/licenses/LICENSE,sha256=tXPvVl3gLVc6e0qCEoLH9KjeA7z4JVL78UybpvGtBCw,1096
7
- k8s_helper_cli-0.2.7.dist-info/METADATA,sha256=CIBvqHcaY3KaHpngGy7nAX_CO2cjxTo9IkWx0tV-JUI,26956
8
- k8s_helper_cli-0.2.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
- k8s_helper_cli-0.2.7.dist-info/entry_points.txt,sha256=IoCMWUZ6mn90LwzQzEy5YkWOwvogDdZ6ycqUWAzCFTQ,50
10
- k8s_helper_cli-0.2.7.dist-info/top_level.txt,sha256=x9A1jflyer-z2cFnkqk5B42juoH2q0fy5hkT9upsTG8,11
11
- k8s_helper_cli-0.2.7.dist-info/RECORD,,