superset-showtime 0.3.2__py3-none-any.whl → 0.4.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.
Potentially problematic release.
This version of superset-showtime might be problematic. Click here for more details.
- showtime/__init__.py +3 -2
- showtime/cli.py +268 -1513
- showtime/core/aws.py +30 -26
- showtime/core/github.py +10 -6
- showtime/core/github_messages.py +4 -4
- showtime/core/pull_request.py +534 -0
- showtime/core/show.py +279 -0
- {superset_showtime-0.3.2.dist-info → superset_showtime-0.4.0.dist-info}/METADATA +17 -9
- superset_showtime-0.4.0.dist-info/RECORD +16 -0
- showtime/commands/__init__.py +0 -1
- showtime/commands/start.py +0 -40
- showtime/core/circus.py +0 -289
- superset_showtime-0.3.2.dist-info/RECORD +0 -17
- {superset_showtime-0.3.2.dist-info → superset_showtime-0.4.0.dist-info}/WHEEL +0 -0
- {superset_showtime-0.3.2.dist-info → superset_showtime-0.4.0.dist-info}/entry_points.txt +0 -0
showtime/core/aws.py
CHANGED
|
@@ -11,7 +11,7 @@ from dataclasses import dataclass
|
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from typing import Any, Dict, List, Optional
|
|
13
13
|
|
|
14
|
-
import boto3
|
|
14
|
+
import boto3 # type: ignore[import-untyped]
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
@dataclass
|
|
@@ -36,7 +36,12 @@ class EnvironmentResult:
|
|
|
36
36
|
class AWSInterface:
|
|
37
37
|
"""AWS ECS/ECR client replicating current GHA logic"""
|
|
38
38
|
|
|
39
|
-
def __init__(
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
region: Optional[str] = None,
|
|
42
|
+
cluster: Optional[str] = None,
|
|
43
|
+
repository: Optional[str] = None,
|
|
44
|
+
):
|
|
40
45
|
self.region = region or os.getenv("AWS_REGION", "us-west-2")
|
|
41
46
|
self.cluster = cluster or os.getenv("ECS_CLUSTER", "superset-ci")
|
|
42
47
|
self.repository = repository or os.getenv("ECR_REPOSITORY", "superset-ci")
|
|
@@ -55,24 +60,23 @@ class AWSInterface:
|
|
|
55
60
|
pr_number: int,
|
|
56
61
|
sha: str,
|
|
57
62
|
github_user: str = "unknown",
|
|
58
|
-
feature_flags: List[Dict[str, str]] = None,
|
|
63
|
+
feature_flags: Optional[List[Dict[str, str]]] = None,
|
|
59
64
|
image_tag_override: Optional[str] = None,
|
|
60
65
|
force: bool = False,
|
|
61
66
|
) -> EnvironmentResult:
|
|
62
67
|
"""
|
|
63
|
-
Create ephemeral environment with
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
1.
|
|
67
|
-
2.
|
|
68
|
-
3.
|
|
69
|
-
4.
|
|
70
|
-
5.
|
|
71
|
-
6. Get public IP and return for traffic switching
|
|
68
|
+
Create ephemeral environment (replaces any existing service with same name)
|
|
69
|
+
|
|
70
|
+
Steps:
|
|
71
|
+
1. Create task definition with feature flags
|
|
72
|
+
2. Delete any existing service with same name (clean slate)
|
|
73
|
+
3. Create fresh ECS service
|
|
74
|
+
4. Deploy and wait for stability
|
|
75
|
+
5. Health check and return IP
|
|
72
76
|
"""
|
|
73
77
|
from datetime import datetime
|
|
74
78
|
|
|
75
|
-
from .
|
|
79
|
+
from .show import Show
|
|
76
80
|
|
|
77
81
|
# Create Show object for consistent AWS naming
|
|
78
82
|
show = Show(
|
|
@@ -126,22 +130,21 @@ class AWSInterface:
|
|
|
126
130
|
if not task_def_arn:
|
|
127
131
|
return EnvironmentResult(success=False, error="Failed to create task definition")
|
|
128
132
|
|
|
129
|
-
# Step 3:
|
|
130
|
-
print(f"🔍 Checking for existing
|
|
133
|
+
# Step 3: Clean slate - Delete any existing service with this exact name
|
|
134
|
+
print(f"🔍 Checking for existing service: {service_name}")
|
|
131
135
|
existing_services = self._find_pr_services(pr_number)
|
|
132
136
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
f"
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
print(f" 🔵 Blue: {svc['service_name']} ({svc['status']})")
|
|
137
|
+
for existing_service in existing_services:
|
|
138
|
+
if existing_service["service_name"] == service_name:
|
|
139
|
+
print(f"🗑️ Deleting existing service: {service_name}")
|
|
140
|
+
self._delete_ecs_service(service_name)
|
|
141
|
+
break
|
|
139
142
|
|
|
140
|
-
# Step 4: Create
|
|
141
|
-
print(f"
|
|
143
|
+
# Step 4: Create fresh service
|
|
144
|
+
print(f"🆕 Creating service: {service_name}")
|
|
142
145
|
success = self._create_ecs_service(service_name, pr_number, github_user, task_def_arn)
|
|
143
146
|
if not success:
|
|
144
|
-
return EnvironmentResult(success=False, error="
|
|
147
|
+
return EnvironmentResult(success=False, error="Service creation failed")
|
|
145
148
|
|
|
146
149
|
# Step 5: Deploy task definition to green service
|
|
147
150
|
success = self._deploy_task_definition(service_name, task_def_arn)
|
|
@@ -254,7 +257,8 @@ class AWSInterface:
|
|
|
254
257
|
return None
|
|
255
258
|
|
|
256
259
|
eni = eni_response["NetworkInterfaces"][0]
|
|
257
|
-
|
|
260
|
+
public_ip = eni.get("Association", {}).get("PublicIp")
|
|
261
|
+
return str(public_ip) if public_ip else None
|
|
258
262
|
|
|
259
263
|
except Exception:
|
|
260
264
|
return None
|
|
@@ -336,7 +340,7 @@ class AWSInterface:
|
|
|
336
340
|
task_def_arn = response["taskDefinition"]["taskDefinitionArn"]
|
|
337
341
|
|
|
338
342
|
print(f"✅ Created task definition: {task_def_arn}")
|
|
339
|
-
return task_def_arn
|
|
343
|
+
return str(task_def_arn)
|
|
340
344
|
|
|
341
345
|
except Exception as e:
|
|
342
346
|
print(f"❌ Task definition creation failed: {e}")
|
showtime/core/github.py
CHANGED
|
@@ -7,7 +7,7 @@ and circus tent emoji state synchronization.
|
|
|
7
7
|
|
|
8
8
|
import os
|
|
9
9
|
from dataclasses import dataclass
|
|
10
|
-
from typing import Dict, List, Optional
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
11
|
|
|
12
12
|
import httpx
|
|
13
13
|
|
|
@@ -23,7 +23,9 @@ class GitHubError(Exception):
|
|
|
23
23
|
class GitHubInterface:
|
|
24
24
|
"""GitHub API client for circus tent label operations"""
|
|
25
25
|
|
|
26
|
-
def __init__(
|
|
26
|
+
def __init__(
|
|
27
|
+
self, token: Optional[str] = None, org: Optional[str] = None, repo: Optional[str] = None
|
|
28
|
+
):
|
|
27
29
|
self.token = token or self._detect_token()
|
|
28
30
|
self.org = org or os.getenv("GITHUB_ORG", "apache")
|
|
29
31
|
self.repo = repo or os.getenv("GITHUB_REPO", "superset")
|
|
@@ -120,16 +122,17 @@ class GitHubInterface:
|
|
|
120
122
|
def get_latest_commit_sha(self, pr_number: int) -> str:
|
|
121
123
|
"""Get the latest commit SHA for a PR"""
|
|
122
124
|
pr_data = self.get_pr_data(pr_number)
|
|
123
|
-
return pr_data["head"]["sha"]
|
|
125
|
+
return str(pr_data["head"]["sha"])
|
|
124
126
|
|
|
125
|
-
def get_pr_data(self, pr_number: int) ->
|
|
127
|
+
def get_pr_data(self, pr_number: int) -> Dict[str, Any]:
|
|
126
128
|
"""Get full PR data including description"""
|
|
127
129
|
url = f"{self.base_url}/repos/{self.org}/{self.repo}/pulls/{pr_number}"
|
|
128
130
|
|
|
129
131
|
with httpx.Client() as client:
|
|
130
132
|
response = client.get(url, headers=self.headers)
|
|
131
133
|
response.raise_for_status()
|
|
132
|
-
|
|
134
|
+
result: Dict[str, Any] = response.json()
|
|
135
|
+
return result
|
|
133
136
|
|
|
134
137
|
def get_circus_labels(self, pr_number: int) -> List[str]:
|
|
135
138
|
"""Get only circus tent emoji labels for a PR"""
|
|
@@ -149,7 +152,7 @@ class GitHubInterface:
|
|
|
149
152
|
# Search for PRs with any circus tent labels
|
|
150
153
|
params = {
|
|
151
154
|
"q": f"repo:{self.org}/{self.repo} is:pr 🎪",
|
|
152
|
-
"per_page": 100,
|
|
155
|
+
"per_page": "100",
|
|
153
156
|
} # Include closed PRs
|
|
154
157
|
|
|
155
158
|
with httpx.Client() as client:
|
|
@@ -205,6 +208,7 @@ class GitHubInterface:
|
|
|
205
208
|
return False # Label doesn't exist
|
|
206
209
|
else:
|
|
207
210
|
response.raise_for_status()
|
|
211
|
+
return False # Should never reach here
|
|
208
212
|
|
|
209
213
|
def cleanup_sha_labels(self, dry_run: bool = False) -> List[str]:
|
|
210
214
|
"""Clean up all circus tent labels with SHA patterns from repository"""
|
showtime/core/github_messages.py
CHANGED
|
@@ -7,7 +7,7 @@ Centralized PR comment functions with type hints and clean formatting.
|
|
|
7
7
|
import os
|
|
8
8
|
from typing import Dict, List, Optional
|
|
9
9
|
|
|
10
|
-
from .
|
|
10
|
+
from .show import Show
|
|
11
11
|
|
|
12
12
|
# AWS Console URL constants
|
|
13
13
|
BASE_AWS_URL = "https://us-west-2.console.aws.amazon.com/ecs/v2/clusters/superset-ci/services"
|
|
@@ -41,7 +41,7 @@ def _create_header_links(sha: str) -> Dict[str, str]:
|
|
|
41
41
|
Returns:
|
|
42
42
|
Dict with showtime_link, gha_link, commit_link
|
|
43
43
|
"""
|
|
44
|
-
from .
|
|
44
|
+
from .show import short_sha
|
|
45
45
|
|
|
46
46
|
repo_path = get_repo_path()
|
|
47
47
|
return {
|
|
@@ -175,7 +175,7 @@ def rolling_start_comment(current_show: Show, new_sha: str) -> str:
|
|
|
175
175
|
current_show: Current Show object with SHA and IP
|
|
176
176
|
new_sha: New environment SHA (full SHA, will be truncated)
|
|
177
177
|
"""
|
|
178
|
-
from .
|
|
178
|
+
from .show import short_sha
|
|
179
179
|
|
|
180
180
|
links = _create_header_links(new_sha)
|
|
181
181
|
header = f"🎪 {links['showtime_link']} is updating {current_show.short_sha}→{short_sha(new_sha)} on {links['gha_link']} for {links['commit_link']}"
|
|
@@ -216,7 +216,7 @@ def rolling_failure_comment(current_show: Show, new_sha: str, error: str) -> str
|
|
|
216
216
|
new_sha: Failed new environment SHA (full SHA, will be truncated)
|
|
217
217
|
error: Error message describing what went wrong
|
|
218
218
|
"""
|
|
219
|
-
from .
|
|
219
|
+
from .show import short_sha
|
|
220
220
|
|
|
221
221
|
links = _create_header_links(new_sha)
|
|
222
222
|
header = f"🎪 {links['showtime_link']} failed updating to {short_sha(new_sha)} on {links['gha_link']} for {links['commit_link']}"
|