superset-showtime 0.5.0__py3-none-any.whl → 0.5.3__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.

Potentially problematic release.


This version of superset-showtime might be problematic. Click here for more details.

showtime/__init__.py CHANGED
@@ -4,7 +4,7 @@
4
4
  Circus tent emoji state tracking for Apache Superset ephemeral environments.
5
5
  """
6
6
 
7
- __version__ = "0.5.0"
7
+ __version__ = "0.5.3"
8
8
  __author__ = "Maxime Beauchemin"
9
9
  __email__ = "maximebeauchemin@gmail.com"
10
10
 
showtime/cli.py CHANGED
@@ -722,7 +722,7 @@ def aws_cleanup(
722
722
  parts = pr_match.split("-")
723
723
  if len(parts) >= 2:
724
724
  pr_number = int(parts[0])
725
- success = aws.delete_environment(service.replace("-service", ""), pr_number)
725
+ success = aws.delete_environment(service, pr_number)
726
726
  if success:
727
727
  p(f"✅ Deleted {service}")
728
728
  deleted_count += 1
showtime/core/aws.py CHANGED
@@ -175,36 +175,62 @@ class AWSInterface:
175
175
 
176
176
  def delete_environment(self, service_name: str, pr_number: int) -> bool:
177
177
  """
178
- Delete ephemeral environment - replicates cleanup GHA logic
178
+ Delete ephemeral environment with proper verification
179
179
 
180
180
  Steps:
181
- 1. Check if ECS service exists and is active
182
- 2. Delete ECS service with --force
181
+ 1. Check if ECS service exists
182
+ 2. Delete ECS service with force and wait for completion
183
183
  3. Delete ECR image tag
184
+ 4. Verify deletion completed
184
185
  """
185
186
  try:
186
- # Step 1: Check if service exists and is active
187
- if not self._service_exists(service_name):
188
- return True # Already deleted
187
+ ecs_service_name = f"{service_name}-service" if not service_name.endswith("-service") else service_name
188
+ print(f"🗑️ Deleting ECS service: {ecs_service_name}")
189
+
190
+ # Step 1: Check if service exists
191
+ if not self._service_exists(ecs_service_name):
192
+ print(f"✅ Service {ecs_service_name} already deleted")
193
+ return True
194
+
195
+ # Step 2: Delete ECS service (force delete) and wait
196
+ print(f"☁️ Initiating ECS service deletion...")
197
+ delete_response = self.ecs_client.delete_service(
198
+ cluster=self.cluster,
199
+ service=ecs_service_name,
200
+ force=True
201
+ )
202
+ print(f"🔄 Delete initiated: {delete_response.get('service', {}).get('status', 'unknown')}")
189
203
 
190
- # Step 2: Delete ECS service (force delete)
191
- self.ecs_client.delete_service(cluster=self.cluster, service=service_name, force=True)
204
+ # Step 3: Wait for deletion to complete (crucial!)
205
+ print(f"⏳ Waiting for service deletion to complete...")
206
+ deletion_success = self._wait_for_service_deletion(ecs_service_name, timeout_minutes=10)
207
+
208
+ if not deletion_success:
209
+ print(f"⚠️ Service deletion timeout - service may still exist")
210
+ return False
192
211
 
193
- # Step 3: Delete ECR image tag
194
- # Extract SHA from service name: pr-1234-abc123f → abc123f
195
- sha = service_name.split("-")[-1]
196
- image_tag = f"pr-{pr_number}-{sha}"
212
+ # Step 4: Delete ECR image tag
213
+ print(f"🐳 Cleaning up Docker image...")
214
+ # Fix SHA extraction: pr-34831-ac533ec-service → ac533ec
215
+ # Remove "pr-" prefix and "-service" suffix, then get SHA (last part)
216
+ base_name = service_name.replace("pr-", "").replace("-service", "")
217
+ parts = base_name.split("-")
218
+ sha = parts[-1] if len(parts) > 1 else base_name # Last part is SHA
219
+ image_tag = f"pr-{pr_number}-{sha}-ci"
197
220
 
198
221
  try:
199
222
  self.ecr_client.batch_delete_image(
200
223
  repositoryName=self.repository, imageIds=[{"imageTag": image_tag}]
201
224
  )
225
+ print(f"✅ Deleted ECR image: {image_tag}")
202
226
  except self.ecr_client.exceptions.ImageNotFoundException:
203
- pass # Image already deleted
227
+ print(f"ℹ️ ECR image {image_tag} already deleted")
204
228
 
229
+ print(f"✅ Environment {service_name} fully deleted")
205
230
  return True
206
231
 
207
232
  except Exception as e:
233
+ print(f"❌ AWS deletion failed: {e}")
208
234
  raise AWSError(
209
235
  message=str(e), operation="delete_environment", resource=service_name
210
236
  ) from e
@@ -246,6 +246,9 @@ class PullRequest:
246
246
  print(f"✅ Deployment completed - environment running at {show.ip}:8080")
247
247
  self._update_show_labels(show, dry_run_github)
248
248
 
249
+ # Blue-green cleanup: stop all other environments for this PR
250
+ cleaned_count = self.stop_previous_environments(show.sha, dry_run_github, dry_run_aws)
251
+
249
252
  # Show AWS console URLs for monitoring
250
253
  self._show_service_urls(show)
251
254
 
@@ -278,6 +281,9 @@ class PullRequest:
278
281
  print(f"✅ Rolling update completed - new environment at {new_show.ip}:8080")
279
282
  self._update_show_labels(new_show, dry_run_github)
280
283
 
284
+ # Blue-green cleanup: stop all other environments for this PR
285
+ cleaned_count = self.stop_previous_environments(new_show.sha, dry_run_github, dry_run_aws)
286
+
281
287
  # Show AWS console URLs for monitoring
282
288
  self._show_service_urls(new_show)
283
289
 
@@ -450,17 +456,31 @@ class PullRequest:
450
456
 
451
457
  # 2. Remove trigger labels (atomic operation)
452
458
  trigger_labels = [label for label in self.labels if "showtime-trigger-" in label]
453
- for trigger_label in trigger_labels:
454
- get_github().remove_label(self.pr_number, trigger_label)
459
+ if trigger_labels:
460
+ print(f"🏷️ Removing trigger labels: {trigger_labels}")
461
+ for trigger_label in trigger_labels:
462
+ get_github().remove_label(self.pr_number, trigger_label)
463
+ else:
464
+ print("🏷️ No trigger labels to remove")
455
465
 
456
466
  # 3. Set building state immediately (claim the PR)
457
467
  if action in ["create_environment", "rolling_update", "auto_sync"]:
458
468
  building_show = self._create_new_show(target_sha)
459
469
  building_show.status = "building"
470
+
460
471
  # Update labels to reflect building state
472
+ print(f"🏷️ Removing existing circus labels...")
461
473
  get_github().remove_circus_labels(self.pr_number)
462
- for label in building_show.to_circus_labels():
463
- get_github().add_label(self.pr_number, label)
474
+
475
+ new_labels = building_show.to_circus_labels()
476
+ print(f"🏷️ Creating new labels: {new_labels}")
477
+ for label in new_labels:
478
+ try:
479
+ get_github().add_label(self.pr_number, label)
480
+ print(f" ✅ Added: {label}")
481
+ except Exception as e:
482
+ print(f" ❌ Failed to add {label}: {e}")
483
+ raise
464
484
 
465
485
  return True
466
486
 
@@ -566,6 +586,17 @@ class PullRequest:
566
586
  for old_status_label in sha_status_labels:
567
587
  get_github().remove_label(self.pr_number, old_status_label)
568
588
 
589
+ # For running environments, ensure only ONE active pointer exists
590
+ if show.status == "running":
591
+ # Remove ALL existing active pointers (there should only be one)
592
+ existing_active_pointers = [
593
+ label for label in self.labels
594
+ if label.startswith("🎪 🎯 ")
595
+ ]
596
+ for old_pointer in existing_active_pointers:
597
+ print(f"🎯 Removing old active pointer: {old_pointer}")
598
+ get_github().remove_label(self.pr_number, old_pointer)
599
+
569
600
  # Now do normal differential updates - only for this SHA
570
601
  current_sha_labels = {
571
602
  label for label in self.labels
@@ -602,3 +633,48 @@ class PullRequest:
602
633
  print(f"📝 Logs: {urls['logs']}")
603
634
  print(f"📊 Service: {urls['service']}")
604
635
  print("")
636
+
637
+ def stop_previous_environments(self, keep_sha: str, dry_run_github: bool = False, dry_run_aws: bool = False) -> int:
638
+ """Stop all environments except the specified SHA (blue-green cleanup)
639
+
640
+ Args:
641
+ keep_sha: SHA of environment to keep running
642
+ dry_run_github: Skip GitHub label operations
643
+ dry_run_aws: Skip AWS operations
644
+
645
+ Returns:
646
+ Number of environments stopped
647
+ """
648
+ stopped_count = 0
649
+
650
+ for show in self.shows:
651
+ if show.sha != keep_sha:
652
+ print(f"🧹 Cleaning up old environment: {show.sha} ({show.status})")
653
+ try:
654
+ show.stop(dry_run_github=dry_run_github, dry_run_aws=dry_run_aws)
655
+
656
+ # Remove labels for this old environment
657
+ if not dry_run_github:
658
+ old_labels = show.to_circus_labels()
659
+ print(f"🏷️ Removing labels for {show.sha}: {len(old_labels)} labels")
660
+ for label in old_labels:
661
+ try:
662
+ get_github().remove_label(self.pr_number, label)
663
+ except Exception as e:
664
+ print(f"⚠️ Failed to remove label {label}: {e}")
665
+
666
+ stopped_count += 1
667
+ print(f"✅ Stopped environment {show.sha}")
668
+
669
+ except Exception as e:
670
+ print(f"❌ Failed to stop environment {show.sha}: {e}")
671
+
672
+ if stopped_count > 0:
673
+ print(f"🧹 Blue-green cleanup: stopped {stopped_count} old environments")
674
+ # Refresh labels after cleanup
675
+ if not dry_run_github:
676
+ self.refresh_labels()
677
+ else:
678
+ print("ℹ️ No old environments to clean up")
679
+
680
+ return stopped_count
showtime/core/show.py CHANGED
@@ -165,17 +165,17 @@ class Show:
165
165
 
166
166
  # Detect if running in CI environment
167
167
  is_ci = bool(os.getenv("GITHUB_ACTIONS") or os.getenv("CI"))
168
-
168
+
169
169
  # Build command without final path
170
170
  cmd = [
171
171
  "docker",
172
- "buildx",
172
+ "buildx",
173
173
  "build",
174
174
  "--push",
175
175
  "--platform",
176
176
  "linux/amd64",
177
177
  "--target",
178
- "dev",
178
+ "showtime",
179
179
  "--build-arg",
180
180
  "INCLUDE_CHROMIUM=false",
181
181
  "--build-arg",
@@ -190,7 +190,7 @@ class Show:
190
190
  cmd.extend([
191
191
  "--cache-from",
192
192
  "type=registry,ref=apache/superset-cache:showtime",
193
- "--cache-to",
193
+ "--cache-to",
194
194
  "type=registry,mode=max,ref=apache/superset-cache:showtime",
195
195
  ])
196
196
  print("🐳 CI environment: Using full registry caching")
@@ -202,16 +202,14 @@ class Show:
202
202
  ])
203
203
  print("🐳 Local environment: Using cache-from only (no export)")
204
204
 
205
- # Add --load only when building for native architecture or explicitly requested
206
- # Intel Mac/Linux can load linux/amd64, Apple Silicon cannot
207
- native_x86 = platform.machine() in ("x86_64", "AMD64")
205
+ # Add --load only when explicitly requested for local testing
208
206
  force_load = os.getenv("DOCKER_LOAD", "false").lower() == "true"
209
-
210
- if native_x86 or force_load:
207
+
208
+ if force_load:
211
209
  cmd.append("--load")
212
- print("🐳 Will load image to local Docker daemon (native x86_64 platform)")
210
+ print("🐳 Will load image to local Docker daemon (DOCKER_LOAD=true)")
213
211
  else:
214
- print("🐳 Cross-platform build - pushing to registry only (no local load)")
212
+ print("🐳 Push-only build (no local load) - faster for CI/deployment")
215
213
 
216
214
  # Add build context path last
217
215
  cmd.append(".")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: superset-showtime
3
- Version: 0.5.0
3
+ Version: 0.5.3
4
4
  Summary: 🎪 Apache Superset ephemeral environment management with circus tent emoji state tracking
5
5
  Project-URL: Homepage, https://github.com/apache/superset-showtime
6
6
  Project-URL: Documentation, https://superset-showtime.readthedocs.io/
@@ -0,0 +1,16 @@
1
+ showtime/__init__.py,sha256=HKR0Quvgeabq-6nUgJV_sgWCHcm5jzwuQR_uOoXJ9VQ,448
2
+ showtime/__main__.py,sha256=EVaDaTX69yIhCzChg99vqvFSCN4ELstEt7Mpb9FMZX8,109
3
+ showtime/cli.py,sha256=cQB5kH-XWFX3MhsxRaChVdjVe_I_7kBVyFY4fQrHPWA,30542
4
+ showtime/core/__init__.py,sha256=54hbdFNGrzuNMBdraezfjT8Zi6g221pKlJ9mREnKwCw,34
5
+ showtime/core/aws.py,sha256=uTjJAvEBQMyTccS93WZeNPhfeKQhJgOQQ0BJdnQjvCU,35007
6
+ showtime/core/emojis.py,sha256=MHEDuPIdfNiop4zbNLuviz3eY05QiftYSHHCVbkfKhw,2129
7
+ showtime/core/github.py,sha256=uETvKDO2Yhpqg3fxLtrKaCuZR3b-1LVmgnf5aLcqrAQ,9988
8
+ showtime/core/github_messages.py,sha256=MfgwCukrEsWWesMsuL8saciDgP4nS-gijzu8DXr-Alg,7450
9
+ showtime/core/label_colors.py,sha256=efhbFnz_3nqEnEqmgyF6_hZbxtCu_fmb68BIIUpSsnk,3895
10
+ showtime/core/pull_request.py,sha256=X0LU717-Bzs5TSmD36DMaNQ9KfpwXMJ74OWr2aBj7X0,27000
11
+ showtime/core/show.py,sha256=FpxDm52LASCJvf8UF998AtNiVzfdYIwNEsPAsOAAwL0,9701
12
+ showtime/data/ecs-task-definition.json,sha256=2acmqoF-3CxaBJP_VDkMMpG_U2RI4VPk1JvFOprMFyc,2098
13
+ superset_showtime-0.5.3.dist-info/METADATA,sha256=nP5JM98Vi0uUgEYBn2lVxsXRVvb4_CuK9U-WgbiBlZE,12052
14
+ superset_showtime-0.5.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
+ superset_showtime-0.5.3.dist-info/entry_points.txt,sha256=rDW7oZ57mqyBUS4N_3_R7bZNGVHB-104jwmY-hHC_ck,85
16
+ superset_showtime-0.5.3.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- showtime/__init__.py,sha256=KTG3yk6KrxuzGE2QA5xFEyUW9K8I4qRJhZUCCaYP1ww,448
2
- showtime/__main__.py,sha256=EVaDaTX69yIhCzChg99vqvFSCN4ELstEt7Mpb9FMZX8,109
3
- showtime/cli.py,sha256=mVGv9xe0ug2rHMtdRy8bETPeH1qwdrfTiIKE4U8opi8,30566
4
- showtime/core/__init__.py,sha256=54hbdFNGrzuNMBdraezfjT8Zi6g221pKlJ9mREnKwCw,34
5
- showtime/core/aws.py,sha256=Pqj-vptEuahPKRV2C6aO66xArhhAIzUkcSSTa-t0r3g,33561
6
- showtime/core/emojis.py,sha256=MHEDuPIdfNiop4zbNLuviz3eY05QiftYSHHCVbkfKhw,2129
7
- showtime/core/github.py,sha256=uETvKDO2Yhpqg3fxLtrKaCuZR3b-1LVmgnf5aLcqrAQ,9988
8
- showtime/core/github_messages.py,sha256=MfgwCukrEsWWesMsuL8saciDgP4nS-gijzu8DXr-Alg,7450
9
- showtime/core/label_colors.py,sha256=efhbFnz_3nqEnEqmgyF6_hZbxtCu_fmb68BIIUpSsnk,3895
10
- showtime/core/pull_request.py,sha256=HZa8_BgIEHCXvhhM3TfnzozAXije3fvi6XacfhPm11c,23498
11
- showtime/core/show.py,sha256=CEG_f5TxbKuBqn8bg9K01_QJOlzWXTR4Cvh4YYgxZRk,9889
12
- showtime/data/ecs-task-definition.json,sha256=2acmqoF-3CxaBJP_VDkMMpG_U2RI4VPk1JvFOprMFyc,2098
13
- superset_showtime-0.5.0.dist-info/METADATA,sha256=y3IGzYt2fe1rtDUXYNuiJWoAHBCyj8Fh665txa1FvqU,12052
14
- superset_showtime-0.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
- superset_showtime-0.5.0.dist-info/entry_points.txt,sha256=rDW7oZ57mqyBUS4N_3_R7bZNGVHB-104jwmY-hHC_ck,85
16
- superset_showtime-0.5.0.dist-info/RECORD,,