k8s-helper-cli 0.2.6__tar.gz → 0.3.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: k8s-helper-cli
3
- Version: 0.2.6
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
 
@@ -546,6 +546,15 @@ k8s-helper create-deployment my-app nginx:latest --replicas 3 --namespace my-nam
546
546
  # Scale a deployment
547
547
  k8s-helper scale-deployment my-app --replicas 5 --namespace my-namespace
548
548
 
549
+ # Perform a rolling update
550
+ k8s-helper rolling-update my-app --image nginx:1.21 --namespace my-namespace
551
+
552
+ # Rolling update with multiple changes
553
+ k8s-helper rolling-update my-app --image nginx:1.21 --replicas 3 --env "ENV=prod" --namespace my-namespace
554
+
555
+ # Rolling update with status monitoring
556
+ k8s-helper rolling-update my-app --image nginx:1.21 --show-status --namespace my-namespace
557
+
549
558
  # Delete a deployment
550
559
  k8s-helper delete-deployment my-app --namespace my-namespace
551
560
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "k8s-helper-cli"
3
- version = "0.2.6"
3
+ version = "0.3.0"
4
4
  description = "A simplified Python wrapper for common Kubernetes operations"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -20,7 +20,7 @@ from .utils import (
20
20
  create_service_manifest
21
21
  )
22
22
 
23
- __version__ = "0.2.6"
23
+ __version__ = "0.3.0"
24
24
  __author__ = "Harshit Chatterjee"
25
25
  __email__ = "harshitchatterjee50@gmail.com"
26
26
 
@@ -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,
@@ -748,65 +859,61 @@ def create_eks_cluster(
748
859
 
749
860
  if wait:
750
861
  console.print("⏳ Waiting for cluster to become active...")
751
- with console.status("Waiting for cluster to be ready..."):
752
- if eks_client.wait_for_cluster_active(name):
753
- console.print("✅ EKS cluster is now active!")
754
-
755
- # Show cluster status
756
- status = eks_client.get_cluster_status(name)
757
- console.print(f"🔗 Endpoint: {status['endpoint']}")
758
-
759
- # If node group was created, wait for it too
760
- if create_nodegroup and 'nodegroup_info' in cluster_info:
761
- nodegroup_name = cluster_info['nodegroup_info']['nodegroup_name']
762
- console.print(f"⏳ Waiting for node group {nodegroup_name} to become active...")
763
- with console.status("Waiting for node group to be ready..."):
764
- if eks_client.wait_for_nodegroup_active(name, nodegroup_name):
765
- console.print("✅ Node group is now active!")
766
- console.print("🎉 Cluster is ready with worker nodes!")
767
- else:
768
- console.print("❌ Timeout waiting for node group to become active")
769
- elif create_nodegroup and not wait:
770
- # Create node group now that cluster is active
771
- nodegroup_name = cluster_info.get('node_group_name') or f"{name}-nodegroup"
772
- console.print(f"🔧 Creating node group: {nodegroup_name}")
773
- try:
774
- with console.status("Creating node group..."):
775
- nodegroup_info = eks_client.create_nodegroup(
776
- cluster_name=name,
777
- nodegroup_name=nodegroup_name,
778
- instance_types=instance_type_list,
779
- ami_type=ami_type,
780
- capacity_type=capacity_type,
781
- scaling_config=scaling_config
782
- # No SSH key for automatic creation
783
- )
784
- console.print(f"✅ Node group creation initiated: {nodegroup_name}")
785
- console.print(f"📋 Node group ARN: {nodegroup_info['nodegroup_arn']}")
786
-
787
- console.print(f"⏳ Waiting for node group to become active...")
788
- with console.status("Waiting for node group to be ready..."):
789
- if eks_client.wait_for_nodegroup_active(name, nodegroup_name):
790
- console.print("✅ Node group is now active!")
791
- console.print("🎉 Cluster is ready with worker nodes!")
792
- else:
793
- console.print("❌ Timeout waiting for node group to become active")
794
- except Exception as e:
795
- console.print(f"❌ Failed to create node group: {e}")
796
-
797
- # Show next steps
798
- console.print(f"\n🚀 Next steps:")
799
- console.print(f" 1. Configure kubectl: aws eks update-kubeconfig --name {name} --region {region}")
800
- if create_nodegroup:
801
- console.print(f" 2. Verify nodes: kubectl get nodes")
802
- console.print(f" 3. Verify connection: kubectl get svc")
803
- console.print(f" 4. Deploy applications: k8s-helper apply <app-name> <image>")
862
+ if eks_client.wait_for_cluster_active(name):
863
+ console.print("✅ EKS cluster is now active!")
864
+
865
+ # Show cluster status
866
+ status = eks_client.get_cluster_status(name)
867
+ console.print(f"🔗 Endpoint: {status['endpoint']}")
868
+
869
+ # If node group was created, wait for it too
870
+ if create_nodegroup and 'nodegroup_info' in cluster_info:
871
+ nodegroup_name = cluster_info['nodegroup_info']['nodegroup_name']
872
+ console.print(f"⏳ Waiting for node group {nodegroup_name} to become active...")
873
+ if eks_client.wait_for_nodegroup_active(name, nodegroup_name):
874
+ console.print(" Node group is now active!")
875
+ console.print("🎉 Cluster is ready with worker nodes!")
804
876
  else:
805
- console.print(f" 2. Create node group: k8s-helper create-nodegroup {name}")
806
- console.print(f" 3. Verify connection: kubectl get svc")
807
- console.print(f" 4. Deploy applications: k8s-helper apply <app-name> <image>")
877
+ console.print(" Timeout waiting for node group to become active")
878
+ elif create_nodegroup and not wait:
879
+ # Create node group now that cluster is active
880
+ nodegroup_name = cluster_info.get('node_group_name') or f"{name}-nodegroup"
881
+ console.print(f"🔧 Creating node group: {nodegroup_name}")
882
+ try:
883
+ nodegroup_info = eks_client.create_nodegroup(
884
+ cluster_name=name,
885
+ nodegroup_name=nodegroup_name,
886
+ instance_types=instance_type_list,
887
+ ami_type=ami_type,
888
+ capacity_type=capacity_type,
889
+ scaling_config=scaling_config
890
+ # No SSH key for automatic creation
891
+ )
892
+ console.print(f"✅ Node group creation initiated: {nodegroup_name}")
893
+ console.print(f"📋 Node group ARN: {nodegroup_info['nodegroup_arn']}")
894
+
895
+ console.print(f"⏳ Waiting for node group to become active...")
896
+ if eks_client.wait_for_nodegroup_active(name, nodegroup_name):
897
+ console.print("✅ Node group is now active!")
898
+ console.print("🎉 Cluster is ready with worker nodes!")
899
+ else:
900
+ console.print("❌ Timeout waiting for node group to become active")
901
+ except Exception as e:
902
+ console.print(f"❌ Failed to create node group: {e}")
903
+
904
+ # Show next steps
905
+ console.print(f"\n🚀 Next steps:")
906
+ console.print(f" 1. Configure kubectl: aws eks update-kubeconfig --name {name} --region {region}")
907
+ if create_nodegroup:
908
+ console.print(f" 2. Verify nodes: kubectl get nodes")
909
+ console.print(f" 3. Verify connection: kubectl get svc")
910
+ console.print(f" 4. Deploy applications: k8s-helper apply <app-name> <image>")
808
911
  else:
809
- console.print(" Timeout waiting for cluster to become active")
912
+ console.print(f" 2. Create node group: k8s-helper create-nodegroup {name}")
913
+ console.print(f" 3. Verify connection: kubectl get svc")
914
+ console.print(f" 4. Deploy applications: k8s-helper apply <app-name> <image>")
915
+ else:
916
+ console.print("❌ Timeout waiting for cluster to become active")
810
917
  else:
811
918
  console.print(f"💡 Use 'aws eks update-kubeconfig --name {name} --region {region}' to configure kubectl")
812
919
 
@@ -907,17 +1014,16 @@ def create_nodegroup(
907
1014
 
908
1015
  if wait:
909
1016
  console.print("⏳ Waiting for node group to become active...")
910
- with console.status("Waiting for node group to be ready..."):
911
- if eks_client.wait_for_nodegroup_active(cluster_name, nodegroup_name):
912
- console.print(" Node group is now active!")
913
- console.print("🎉 You can now deploy workloads!")
914
-
915
- # Show next steps
916
- console.print(f"\n🚀 Next steps:")
917
- console.print(f" 1. Verify nodes: kubectl get nodes")
918
- console.print(f" 2. Deploy applications: k8s-helper apply <app-name> <image>")
919
- else:
920
- console.print("❌ Timeout waiting for node group to become active")
1017
+ if eks_client.wait_for_nodegroup_active(cluster_name, nodegroup_name):
1018
+ console.print("✅ Node group is now active!")
1019
+ console.print("🎉 You can now deploy workloads!")
1020
+
1021
+ # Show next steps
1022
+ console.print(f"\n🚀 Next steps:")
1023
+ console.print(f" 1. Verify nodes: kubectl get nodes")
1024
+ console.print(f" 2. Deploy applications: k8s-helper apply <app-name> <image>")
1025
+ else:
1026
+ console.print("❌ Timeout waiting for node group to become active")
921
1027
  else:
922
1028
  console.print(f"💡 Use 'kubectl get nodes' to check when nodes are ready")
923
1029
 
@@ -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.6
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
 
File without changes
File without changes