superset-showtime 0.5.12__py3-none-any.whl → 0.5.18__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 +1 -1
- showtime/core/emojis.py +1 -0
- showtime/core/label_colors.py +4 -0
- showtime/core/pull_request.py +110 -26
- showtime/core/show.py +23 -17
- {superset_showtime-0.5.12.dist-info → superset_showtime-0.5.18.dist-info}/METADATA +1 -1
- {superset_showtime-0.5.12.dist-info → superset_showtime-0.5.18.dist-info}/RECORD +9 -9
- {superset_showtime-0.5.12.dist-info → superset_showtime-0.5.18.dist-info}/WHEEL +0 -0
- {superset_showtime-0.5.12.dist-info → superset_showtime-0.5.18.dist-info}/entry_points.txt +0 -0
showtime/__init__.py
CHANGED
showtime/core/emojis.py
CHANGED
|
@@ -13,6 +13,7 @@ EMOJI_MEANINGS = {
|
|
|
13
13
|
"🚦": "status", # Traffic light for environment status
|
|
14
14
|
"🏗️": "building", # Construction for building environments
|
|
15
15
|
"🎯": "active", # Target for currently active environment
|
|
16
|
+
"🔒": "blocked", # Lock for blocking all operations
|
|
16
17
|
# Metadata
|
|
17
18
|
"📅": "created_at", # Calendar for creation timestamp
|
|
18
19
|
"🌐": "ip", # Globe for IP address
|
showtime/core/label_colors.py
CHANGED
|
@@ -31,6 +31,10 @@ LABEL_DEFINITIONS = {
|
|
|
31
31
|
"color": "FFE4B5", # Light orange
|
|
32
32
|
"description": "Freeze PR - prevent auto-sync on new commits",
|
|
33
33
|
},
|
|
34
|
+
"🎪 🔒 showtime-blocked": {
|
|
35
|
+
"color": "dc3545", # Red - blocking/danger
|
|
36
|
+
"description": "Block all Showtime operations - maintenance mode",
|
|
37
|
+
},
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
# Status-specific label patterns (generated dynamically)
|
showtime/core/pull_request.py
CHANGED
|
@@ -90,20 +90,10 @@ class PullRequest:
|
|
|
90
90
|
|
|
91
91
|
@property
|
|
92
92
|
def building_show(self) -> Optional[Show]:
|
|
93
|
-
"""The currently building show (from
|
|
94
|
-
building_sha = None
|
|
95
|
-
for label in self.labels:
|
|
96
|
-
if label.startswith("🎪 🏗️ "):
|
|
97
|
-
building_sha = label.split(" ")[2]
|
|
98
|
-
break
|
|
99
|
-
|
|
100
|
-
if not building_sha:
|
|
101
|
-
return None
|
|
102
|
-
|
|
93
|
+
"""The currently building show (from building/deploying status)"""
|
|
103
94
|
for show in self.shows:
|
|
104
|
-
if show.
|
|
95
|
+
if show.status in ["building", "deploying"]:
|
|
105
96
|
return show
|
|
106
|
-
|
|
107
97
|
return None
|
|
108
98
|
|
|
109
99
|
@property
|
|
@@ -185,6 +175,88 @@ class PullRequest:
|
|
|
185
175
|
for label in circus_labels:
|
|
186
176
|
self.remove_label(label)
|
|
187
177
|
|
|
178
|
+
def set_show_status(self, show: Show, new_status: str) -> None:
|
|
179
|
+
"""Atomically update show status with thorough label cleanup"""
|
|
180
|
+
show.status = new_status
|
|
181
|
+
|
|
182
|
+
# 1. Refresh labels to get current GitHub state
|
|
183
|
+
self.refresh_labels()
|
|
184
|
+
|
|
185
|
+
# 2. Remove ALL existing status labels for this SHA (not just the "expected" one)
|
|
186
|
+
status_labels_to_remove = [
|
|
187
|
+
label for label in self.labels if label.startswith(f"🎪 {show.sha} 🚦 ")
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
for label in status_labels_to_remove:
|
|
191
|
+
self.remove_label(label)
|
|
192
|
+
|
|
193
|
+
# 3. Add the new status label
|
|
194
|
+
new_status_label = f"🎪 {show.sha} 🚦 {new_status}"
|
|
195
|
+
self.add_label(new_status_label)
|
|
196
|
+
|
|
197
|
+
def set_active_show(self, show: Show) -> None:
|
|
198
|
+
"""Atomically set this show as the active environment"""
|
|
199
|
+
from .emojis import CIRCUS_PREFIX, MEANING_TO_EMOJI
|
|
200
|
+
|
|
201
|
+
# 1. Refresh to get current state
|
|
202
|
+
self.refresh_labels()
|
|
203
|
+
|
|
204
|
+
# 2. Remove ALL existing active pointers (ensure only one)
|
|
205
|
+
active_emoji = MEANING_TO_EMOJI["active"] # Gets 🎯
|
|
206
|
+
active_prefix = f"{CIRCUS_PREFIX} {active_emoji} " # "🎪 🎯 "
|
|
207
|
+
active_pointers = [label for label in self.labels if label.startswith(active_prefix)]
|
|
208
|
+
|
|
209
|
+
for pointer in active_pointers:
|
|
210
|
+
self.remove_label(pointer)
|
|
211
|
+
|
|
212
|
+
# 3. Set this show as the new active one
|
|
213
|
+
active_pointer = f"{active_prefix}{show.sha}" # "🎪 🎯 abc123f"
|
|
214
|
+
self.add_label(active_pointer)
|
|
215
|
+
|
|
216
|
+
def _check_authorization(self) -> bool:
|
|
217
|
+
"""Check if current GitHub actor is authorized for operations"""
|
|
218
|
+
import os
|
|
219
|
+
|
|
220
|
+
import httpx
|
|
221
|
+
|
|
222
|
+
# Only check in GitHub Actions context
|
|
223
|
+
if os.getenv("GITHUB_ACTIONS") != "true":
|
|
224
|
+
return True
|
|
225
|
+
|
|
226
|
+
actor = os.getenv("GITHUB_ACTOR")
|
|
227
|
+
if not actor:
|
|
228
|
+
return True # No actor info, allow operation
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
# Use existing GitHubInterface for consistency
|
|
232
|
+
github = get_github()
|
|
233
|
+
|
|
234
|
+
# Check collaborator permissions
|
|
235
|
+
perm_url = f"{github.base_url}/repos/{github.org}/{github.repo}/collaborators/{actor}/permission"
|
|
236
|
+
|
|
237
|
+
with httpx.Client() as client:
|
|
238
|
+
response = client.get(perm_url, headers=github.headers)
|
|
239
|
+
if response.status_code == 404:
|
|
240
|
+
return False # Not a collaborator
|
|
241
|
+
response.raise_for_status()
|
|
242
|
+
|
|
243
|
+
data = response.json()
|
|
244
|
+
permission = data.get("permission", "none")
|
|
245
|
+
|
|
246
|
+
# Allow write and admin permissions only
|
|
247
|
+
authorized = permission in ["write", "admin"]
|
|
248
|
+
|
|
249
|
+
if not authorized:
|
|
250
|
+
print(f"🚨 Unauthorized actor {actor} (permission: {permission})")
|
|
251
|
+
# Set blocked label for security
|
|
252
|
+
self.add_label("🎪 🔒 showtime-blocked")
|
|
253
|
+
|
|
254
|
+
return authorized
|
|
255
|
+
|
|
256
|
+
except Exception as e:
|
|
257
|
+
print(f"⚠️ Authorization check failed: {e}")
|
|
258
|
+
return True # Fail open for non-security operations
|
|
259
|
+
|
|
188
260
|
def analyze(self, target_sha: str, pr_state: str = "open") -> AnalysisResult:
|
|
189
261
|
"""Analyze what actions are needed (read-only, for --check-only)
|
|
190
262
|
|
|
@@ -208,7 +280,7 @@ class PullRequest:
|
|
|
208
280
|
build_needed = action_needed in ["create_environment", "rolling_update", "auto_sync"]
|
|
209
281
|
|
|
210
282
|
# Determine if sync execution is needed
|
|
211
|
-
sync_needed = action_needed
|
|
283
|
+
sync_needed = action_needed not in ["no_action", "blocked"]
|
|
212
284
|
|
|
213
285
|
return AnalysisResult(
|
|
214
286
|
action_needed=action_needed,
|
|
@@ -244,7 +316,15 @@ class PullRequest:
|
|
|
244
316
|
# 1. Determine what action is needed
|
|
245
317
|
action_needed = self._determine_action(target_sha)
|
|
246
318
|
|
|
247
|
-
# 2.
|
|
319
|
+
# 2. Check for blocked state (fast bailout)
|
|
320
|
+
if action_needed == "blocked":
|
|
321
|
+
return SyncResult(
|
|
322
|
+
success=False,
|
|
323
|
+
action_taken="blocked",
|
|
324
|
+
error="🔒 Showtime operations are blocked for this PR. Remove '🎪 🔒 showtime-blocked' label to re-enable.",
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# 3. Atomic claim for environment changes (PR-level lock)
|
|
248
328
|
if action_needed in ["create_environment", "rolling_update", "auto_sync"]:
|
|
249
329
|
print(f"🔒 Claiming environment for {action_needed}...")
|
|
250
330
|
if not self._atomic_claim(target_sha, action_needed, dry_run_github):
|
|
@@ -266,14 +346,14 @@ class PullRequest:
|
|
|
266
346
|
# Phase 1: Docker build
|
|
267
347
|
print("🐳 Building Docker image...")
|
|
268
348
|
show.build_docker(dry_run_docker)
|
|
269
|
-
show.status = "built"
|
|
270
349
|
print("✅ Docker build completed")
|
|
271
|
-
self._update_show_labels(show, dry_run_github)
|
|
272
350
|
|
|
273
351
|
# Phase 2: AWS deployment
|
|
274
352
|
print("☁️ Deploying to AWS ECS...")
|
|
353
|
+
self.set_show_status(show, "deploying")
|
|
275
354
|
show.deploy_aws(dry_run_aws)
|
|
276
|
-
show
|
|
355
|
+
self.set_show_status(show, "running")
|
|
356
|
+
self.set_active_show(show)
|
|
277
357
|
print(f"✅ Deployment completed - environment running at {show.ip}:8080")
|
|
278
358
|
self._update_show_labels(show, dry_run_github)
|
|
279
359
|
|
|
@@ -303,14 +383,14 @@ class PullRequest:
|
|
|
303
383
|
# Phase 1: Docker build
|
|
304
384
|
print("🐳 Building updated Docker image...")
|
|
305
385
|
new_show.build_docker(dry_run_docker)
|
|
306
|
-
new_show.status = "built"
|
|
307
386
|
print("✅ Docker build completed")
|
|
308
|
-
self._update_show_labels(new_show, dry_run_github)
|
|
309
387
|
|
|
310
388
|
# Phase 2: Blue-green deployment
|
|
311
389
|
print("☁️ Deploying updated environment...")
|
|
390
|
+
self.set_show_status(new_show, "deploying")
|
|
312
391
|
new_show.deploy_aws(dry_run_aws)
|
|
313
|
-
new_show
|
|
392
|
+
self.set_show_status(new_show, "running")
|
|
393
|
+
self.set_active_show(new_show)
|
|
314
394
|
print(f"✅ Rolling update completed - new environment at {new_show.ip}:8080")
|
|
315
395
|
self._update_show_labels(new_show, dry_run_github)
|
|
316
396
|
|
|
@@ -406,7 +486,7 @@ class PullRequest:
|
|
|
406
486
|
if any(label == f"🎪 🎯 {show.sha}" for label in pr.labels):
|
|
407
487
|
show_type = "active"
|
|
408
488
|
# Check for building pointer
|
|
409
|
-
elif
|
|
489
|
+
elif show.status in ["building", "deploying"]:
|
|
410
490
|
show_type = "building"
|
|
411
491
|
# No pointer = orphaned
|
|
412
492
|
|
|
@@ -433,6 +513,14 @@ class PullRequest:
|
|
|
433
513
|
# CRITICAL: Get fresh labels before any decisions
|
|
434
514
|
self.refresh_labels()
|
|
435
515
|
|
|
516
|
+
# Check for blocked state first (fast bailout)
|
|
517
|
+
if "🎪 🔒 showtime-blocked" in self.labels:
|
|
518
|
+
return "blocked"
|
|
519
|
+
|
|
520
|
+
# Check authorization (security layer)
|
|
521
|
+
if not self._check_authorization():
|
|
522
|
+
return "blocked"
|
|
523
|
+
|
|
436
524
|
target_sha_short = target_sha[:7] # Ensure we're working with short SHA
|
|
437
525
|
|
|
438
526
|
# Get the specific show for the target SHA
|
|
@@ -515,7 +603,6 @@ class PullRequest:
|
|
|
515
603
|
for label in new_labels:
|
|
516
604
|
try:
|
|
517
605
|
self.add_label(label)
|
|
518
|
-
print(f" ✅ Added: {label}")
|
|
519
606
|
except Exception as e:
|
|
520
607
|
print(f" ❌ Failed to add {label}: {e}")
|
|
521
608
|
raise
|
|
@@ -646,7 +733,6 @@ class PullRequest:
|
|
|
646
733
|
and (
|
|
647
734
|
label.startswith(f"🎪 {show.sha} ") # SHA-first format: 🎪 abc123f 📅 ...
|
|
648
735
|
or label.startswith(f"🎪 🎯 {show.sha}") # Pointer format: 🎪 🎯 abc123f
|
|
649
|
-
or label.startswith(f"🎪 🏗️ {show.sha}") # Building pointer: 🎪 🏗️ abc123f
|
|
650
736
|
)
|
|
651
737
|
}
|
|
652
738
|
desired_labels = set(show.to_circus_labels())
|
|
@@ -704,9 +790,7 @@ class PullRequest:
|
|
|
704
790
|
existing_labels = [
|
|
705
791
|
label
|
|
706
792
|
for label in self.labels
|
|
707
|
-
if label.startswith(f"🎪 {show.sha} ")
|
|
708
|
-
or label == f"🎪 🎯 {show.sha}"
|
|
709
|
-
or label == f"🎪 🏗️ {show.sha}"
|
|
793
|
+
if label.startswith(f"🎪 {show.sha} ") or label == f"🎪 🎯 {show.sha}"
|
|
710
794
|
]
|
|
711
795
|
print(f"🏷️ Removing existing labels for {show.sha}: {existing_labels}")
|
|
712
796
|
for label in existing_labels:
|
showtime/core/show.py
CHANGED
|
@@ -96,21 +96,24 @@ class Show:
|
|
|
96
96
|
|
|
97
97
|
def to_circus_labels(self) -> List[str]:
|
|
98
98
|
"""Convert show state to circus tent emoji labels (per-SHA format)"""
|
|
99
|
+
from .emojis import CIRCUS_PREFIX, MEANING_TO_EMOJI
|
|
100
|
+
|
|
99
101
|
if not self.created_at:
|
|
100
102
|
self.created_at = datetime.utcnow().strftime("%Y-%m-%dT%H-%M")
|
|
101
103
|
|
|
102
104
|
labels = [
|
|
103
|
-
f"
|
|
104
|
-
f"
|
|
105
|
-
f"
|
|
106
|
-
f"🎪 {self.sha} ⌛ {self.ttl}", # SHA-first TTL
|
|
105
|
+
f"{CIRCUS_PREFIX} {self.sha} {MEANING_TO_EMOJI['status']} {self.status}", # SHA-first status
|
|
106
|
+
f"{CIRCUS_PREFIX} {self.sha} {MEANING_TO_EMOJI['created_at']} {self.created_at}", # SHA-first timestamp
|
|
107
|
+
f"{CIRCUS_PREFIX} {self.sha} {MEANING_TO_EMOJI['ttl']} {self.ttl}", # SHA-first TTL
|
|
107
108
|
]
|
|
108
109
|
|
|
109
110
|
if self.ip:
|
|
110
|
-
labels.append(f"
|
|
111
|
+
labels.append(f"{CIRCUS_PREFIX} {self.sha} {MEANING_TO_EMOJI['ip']} {self.ip}:8080")
|
|
111
112
|
|
|
112
113
|
if self.requested_by:
|
|
113
|
-
labels.append(
|
|
114
|
+
labels.append(
|
|
115
|
+
f"{CIRCUS_PREFIX} {self.sha} {MEANING_TO_EMOJI['requested_by']} {self.requested_by}"
|
|
116
|
+
)
|
|
114
117
|
|
|
115
118
|
return labels
|
|
116
119
|
|
|
@@ -158,7 +161,6 @@ class Show:
|
|
|
158
161
|
def _build_docker_image(self) -> None:
|
|
159
162
|
"""Build Docker image for this environment"""
|
|
160
163
|
import os
|
|
161
|
-
import platform
|
|
162
164
|
import subprocess
|
|
163
165
|
|
|
164
166
|
tag = f"apache/superset:pr-{self.pr_number}-{self.sha}-ci"
|
|
@@ -187,19 +189,23 @@ class Show:
|
|
|
187
189
|
# Add caching based on environment
|
|
188
190
|
if is_ci:
|
|
189
191
|
# Full registry caching in CI (Docker driver supports it)
|
|
190
|
-
cmd.extend(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
192
|
+
cmd.extend(
|
|
193
|
+
[
|
|
194
|
+
"--cache-from",
|
|
195
|
+
"type=registry,ref=apache/superset-cache:showtime",
|
|
196
|
+
"--cache-to",
|
|
197
|
+
"type=registry,mode=max,ref=apache/superset-cache:showtime",
|
|
198
|
+
]
|
|
199
|
+
)
|
|
196
200
|
print("🐳 CI environment: Using full registry caching")
|
|
197
201
|
else:
|
|
198
202
|
# Local build: cache-from only (no cache export)
|
|
199
|
-
cmd.extend(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
+
cmd.extend(
|
|
204
|
+
[
|
|
205
|
+
"--cache-from",
|
|
206
|
+
"type=registry,ref=apache/superset-cache:showtime",
|
|
207
|
+
]
|
|
208
|
+
)
|
|
203
209
|
print("🐳 Local environment: Using cache-from only (no export)")
|
|
204
210
|
|
|
205
211
|
# Add --load only when explicitly requested for local testing
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: superset-showtime
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.18
|
|
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/
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
showtime/__init__.py,sha256=
|
|
1
|
+
showtime/__init__.py,sha256=apR60gqUgO9WuV06_c-B1cXzwzdQRcEaY-uYSPrrX0A,449
|
|
2
2
|
showtime/__main__.py,sha256=EVaDaTX69yIhCzChg99vqvFSCN4ELstEt7Mpb9FMZX8,109
|
|
3
3
|
showtime/cli.py,sha256=8vIJT5TiqXuHDGxRBg6jV3oNv5nKrmDOs5OgltycPeI,31664
|
|
4
4
|
showtime/core/__init__.py,sha256=54hbdFNGrzuNMBdraezfjT8Zi6g221pKlJ9mREnKwCw,34
|
|
5
5
|
showtime/core/aws.py,sha256=uTjJAvEBQMyTccS93WZeNPhfeKQhJgOQQ0BJdnQjvCU,35007
|
|
6
|
-
showtime/core/emojis.py,sha256=
|
|
6
|
+
showtime/core/emojis.py,sha256=arK0N5Q5FLkvOkci-lacb3WS56LTvY8NjYRqt_lhP9s,2188
|
|
7
7
|
showtime/core/git_validation.py,sha256=3dmSGpMDplDAmKWHUyoUEPgt3__8oTuBZxbfuhocT00,6831
|
|
8
8
|
showtime/core/github.py,sha256=gMPJ5TOT6DdZk4y0XqW-C69I7O8A4eI40TgT4IFPqhQ,9623
|
|
9
9
|
showtime/core/github_messages.py,sha256=MfgwCukrEsWWesMsuL8saciDgP4nS-gijzu8DXr-Alg,7450
|
|
10
|
-
showtime/core/label_colors.py,sha256=
|
|
11
|
-
showtime/core/pull_request.py,sha256=
|
|
12
|
-
showtime/core/show.py,sha256=
|
|
10
|
+
showtime/core/label_colors.py,sha256=gSe7EIMl4YjWkIgKHUvuaRSwgEB_B-NYQBxFFlF8Z3s,4065
|
|
11
|
+
showtime/core/pull_request.py,sha256=r-4tCjEjsZOcTk4cjw57yyhNzq1sLPt4SYa1S7RcDlQ,31998
|
|
12
|
+
showtime/core/show.py,sha256=sOgZvGXwdcNDsidF1F_XwPXlSeTb8-Zeqhqb8w1pqAM,9973
|
|
13
13
|
showtime/data/ecs-task-definition.json,sha256=d-NLkIhvr4C6AnwDfDIwUTx-6KFMH9wRkt6pVCbqZY4,2365
|
|
14
|
-
superset_showtime-0.5.
|
|
15
|
-
superset_showtime-0.5.
|
|
16
|
-
superset_showtime-0.5.
|
|
17
|
-
superset_showtime-0.5.
|
|
14
|
+
superset_showtime-0.5.18.dist-info/METADATA,sha256=aTfADzrQ7S-eVsJJDlOvIOaDeSJ4WHqdfW0MtKsqFpg,12053
|
|
15
|
+
superset_showtime-0.5.18.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
16
|
+
superset_showtime-0.5.18.dist-info/entry_points.txt,sha256=rDW7oZ57mqyBUS4N_3_R7bZNGVHB-104jwmY-hHC_ck,85
|
|
17
|
+
superset_showtime-0.5.18.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|