superset-showtime 0.1.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.

@@ -0,0 +1,214 @@
1
+ """
2
+ ๐ŸŽช GitHub API interface for circus tent label management
3
+
4
+ Handles all GitHub operations including PR fetching, label management,
5
+ and circus tent emoji state synchronization.
6
+ """
7
+
8
+ import os
9
+ from dataclasses import dataclass
10
+ from typing import Dict, List, Optional
11
+
12
+ import httpx
13
+
14
+
15
+ @dataclass
16
+ class GitHubError(Exception):
17
+ """GitHub API error"""
18
+
19
+ message: str
20
+ status_code: Optional[int] = None
21
+
22
+
23
+ class GitHubInterface:
24
+ """GitHub API client for circus tent label operations"""
25
+
26
+ def __init__(self, token: str = None, org: str = None, repo: str = None):
27
+ self.token = token or self._detect_token()
28
+ self.org = org or os.getenv("GITHUB_ORG", "apache")
29
+ self.repo = repo or os.getenv("GITHUB_REPO", "superset")
30
+ self.base_url = "https://api.github.com"
31
+
32
+ if not self.token:
33
+ raise GitHubError("GitHub token required. Set GITHUB_TOKEN environment variable.")
34
+
35
+ def _detect_token(self) -> Optional[str]:
36
+ """Detect GitHub token from environment or gh CLI"""
37
+ # 1. Environment variable (GHA style)
38
+ token = os.getenv("GITHUB_TOKEN")
39
+ if token:
40
+ return token
41
+
42
+ # 2. GitHub CLI (local development)
43
+ try:
44
+ import subprocess
45
+
46
+ result = subprocess.run(["gh", "auth", "token"], capture_output=True, text=True)
47
+ if result.returncode == 0:
48
+ return result.stdout.strip()
49
+ except FileNotFoundError:
50
+ pass # gh CLI not installed
51
+
52
+ return None
53
+
54
+ @property
55
+ def headers(self) -> Dict[str, str]:
56
+ """HTTP headers for GitHub API requests"""
57
+ return {
58
+ "Authorization": f"Bearer {self.token}",
59
+ "Accept": "application/vnd.github.v3+json",
60
+ "X-GitHub-Api-Version": "2022-11-28",
61
+ }
62
+
63
+ def get_labels(self, pr_number: int) -> List[str]:
64
+ """Get all labels for a PR"""
65
+ url = f"{self.base_url}/repos/{self.org}/{self.repo}/issues/{pr_number}/labels"
66
+
67
+ with httpx.Client() as client:
68
+ response = client.get(url, headers=self.headers)
69
+ response.raise_for_status()
70
+
71
+ labels_data = response.json()
72
+ return [label["name"] for label in labels_data]
73
+
74
+ def add_label(self, pr_number: int, label: str) -> None:
75
+ """Add a label to a PR"""
76
+ url = f"{self.base_url}/repos/{self.org}/{self.repo}/issues/{pr_number}/labels"
77
+
78
+ with httpx.Client() as client:
79
+ response = client.post(url, headers=self.headers, json={"labels": [label]})
80
+ response.raise_for_status()
81
+
82
+ def remove_label(self, pr_number: int, label: str) -> None:
83
+ """Remove a label from a PR"""
84
+ # URL encode the label name for special characters like emojis
85
+ import urllib.parse
86
+
87
+ encoded_label = urllib.parse.quote(label, safe="")
88
+ url = f"{self.base_url}/repos/{self.org}/{self.repo}/issues/{pr_number}/labels/{encoded_label}"
89
+
90
+ with httpx.Client() as client:
91
+ response = client.delete(url, headers=self.headers)
92
+ # 404 is OK - label might not exist
93
+ if response.status_code not in (200, 204, 404):
94
+ response.raise_for_status()
95
+
96
+ def set_labels(self, pr_number: int, labels: List[str]) -> None:
97
+ """Replace all labels on a PR"""
98
+ url = f"{self.base_url}/repos/{self.org}/{self.repo}/issues/{pr_number}/labels"
99
+
100
+ with httpx.Client() as client:
101
+ response = client.put(url, headers=self.headers, json={"labels": labels})
102
+ response.raise_for_status()
103
+
104
+ def get_latest_commit_sha(self, pr_number: int) -> str:
105
+ """Get the latest commit SHA for a PR"""
106
+ pr_data = self.get_pr_data(pr_number)
107
+ return pr_data["head"]["sha"]
108
+
109
+ def get_pr_data(self, pr_number: int) -> dict:
110
+ """Get full PR data including description"""
111
+ url = f"{self.base_url}/repos/{self.org}/{self.repo}/pulls/{pr_number}"
112
+
113
+ with httpx.Client() as client:
114
+ response = client.get(url, headers=self.headers)
115
+ response.raise_for_status()
116
+ return response.json()
117
+
118
+ def get_circus_labels(self, pr_number: int) -> List[str]:
119
+ """Get only circus tent emoji labels for a PR"""
120
+ all_labels = self.get_labels(pr_number)
121
+ return [label for label in all_labels if label.startswith("๐ŸŽช ")]
122
+
123
+ def remove_circus_labels(self, pr_number: int) -> None:
124
+ """Remove all circus tent labels from a PR"""
125
+ circus_labels = self.get_circus_labels(pr_number)
126
+ for label in circus_labels:
127
+ self.remove_label(pr_number, label)
128
+
129
+ def find_prs_with_shows(self) -> List[int]:
130
+ """Find all PRs that have circus tent labels"""
131
+ # Search for issues with circus tent labels (updated for SHA-first format)
132
+ url = f"{self.base_url}/search/issues"
133
+ # Search for PRs with any circus tent labels
134
+ params = {
135
+ "q": f"repo:{self.org}/{self.repo} is:pr ๐ŸŽช",
136
+ "per_page": 100,
137
+ } # Include closed PRs
138
+
139
+ with httpx.Client() as client:
140
+ response = client.get(url, headers=self.headers, params=params)
141
+ response.raise_for_status()
142
+
143
+ issues = response.json()["items"]
144
+ return [issue["number"] for issue in issues]
145
+
146
+ def post_comment(self, pr_number: int, body: str) -> None:
147
+ """Post a comment on a PR"""
148
+ url = f"{self.base_url}/repos/{self.org}/{self.repo}/issues/{pr_number}/comments"
149
+
150
+ with httpx.Client() as client:
151
+ response = client.post(url, headers=self.headers, json={"body": body})
152
+ response.raise_for_status()
153
+
154
+ def validate_connection(self) -> bool:
155
+ """Test GitHub API connection"""
156
+ try:
157
+ url = f"{self.base_url}/repos/{self.org}/{self.repo}"
158
+ with httpx.Client() as client:
159
+ response = client.get(url, headers=self.headers)
160
+ response.raise_for_status()
161
+ return True
162
+ except Exception:
163
+ return False
164
+
165
+ def get_repository_labels(self) -> List[str]:
166
+ """Get all labels defined in the repository"""
167
+ url = f"{self.base_url}/repos/{self.org}/{self.repo}/labels"
168
+
169
+ with httpx.Client() as client:
170
+ response = client.get(url, headers=self.headers, params={"per_page": 100})
171
+ response.raise_for_status()
172
+
173
+ labels_data = response.json()
174
+ return [label["name"] for label in labels_data]
175
+
176
+ def delete_repository_label(self, label_name: str) -> bool:
177
+ """Delete a label definition from the repository"""
178
+ import urllib.parse
179
+
180
+ encoded_label = urllib.parse.quote(label_name, safe="")
181
+ url = f"{self.base_url}/repos/{self.org}/{self.repo}/labels/{encoded_label}"
182
+
183
+ with httpx.Client() as client:
184
+ response = client.delete(url, headers=self.headers)
185
+ # 404 is OK - label might not exist
186
+ if response.status_code in (200, 204):
187
+ return True
188
+ elif response.status_code == 404:
189
+ return False # Label doesn't exist
190
+ else:
191
+ response.raise_for_status()
192
+
193
+ def cleanup_sha_labels(self, dry_run: bool = False) -> List[str]:
194
+ """Clean up all circus tent labels with SHA patterns from repository"""
195
+ import re
196
+
197
+ all_labels = self.get_repository_labels()
198
+ sha_labels = []
199
+
200
+ # Find labels with SHA patterns (7+ hex chars after ๐ŸŽช)
201
+ sha_pattern = re.compile(r"^๐ŸŽช .* [a-f0-9]{7,}( .*)?$")
202
+
203
+ for label in all_labels:
204
+ if sha_pattern.match(label):
205
+ sha_labels.append(label)
206
+
207
+ if not dry_run:
208
+ deleted_labels = []
209
+ for label in sha_labels:
210
+ if self.delete_repository_label(label):
211
+ deleted_labels.append(label)
212
+ return deleted_labels
213
+
214
+ return sha_labels
@@ -0,0 +1,59 @@
1
+ {
2
+ "containerDefinitions": [
3
+ {
4
+ "name": "superset-ci",
5
+ "image": "apache/superset:latest",
6
+ "cpu": 0,
7
+ "links": [],
8
+ "portMappings": [
9
+ {
10
+ "containerPort": 8080,
11
+ "hostPort": 8080,
12
+ "protocol": "tcp"
13
+ }
14
+ ],
15
+ "essential": true,
16
+ "entryPoint": [],
17
+ "command": [],
18
+ "environment": [
19
+ {
20
+ "name": "SUPERSET_LOAD_EXAMPLES",
21
+ "value": "yes"
22
+ },
23
+ {
24
+ "name": "SUPERSET_PORT",
25
+ "value": "8080"
26
+ },
27
+ {
28
+ "name": "SUPERSET_SECRET_KEY",
29
+ "value": "super-secret-for-ephemerals"
30
+ },
31
+ {
32
+ "name": "TALISMAN_ENABLED",
33
+ "value": "False"
34
+ }
35
+ ],
36
+ "mountPoints": [],
37
+ "volumesFrom": [],
38
+ "logConfiguration": {
39
+ "logDriver": "awslogs",
40
+ "options": {
41
+ "awslogs-group": "/ecs/superset-ci",
42
+ "awslogs-region": "us-west-2",
43
+ "awslogs-stream-prefix": "ecs"
44
+ }
45
+ }
46
+ }
47
+ ],
48
+ "family": "superset-ci",
49
+ "taskRoleArn": "ecsTaskExecutionRole",
50
+ "executionRoleArn": "ecsTaskExecutionRole",
51
+ "networkMode": "awsvpc",
52
+ "volumes": [],
53
+ "placementConstraints": [],
54
+ "requiresCompatibilities": [
55
+ "FARGATE"
56
+ ],
57
+ "cpu": "512",
58
+ "memory": "1024"
59
+ }
@@ -0,0 +1,391 @@
1
+ Metadata-Version: 2.4
2
+ Name: superset-showtime
3
+ Version: 0.1.0
4
+ Summary: ๐ŸŽช Apache Superset ephemeral environment management with circus tent emoji state tracking
5
+ Project-URL: Homepage, https://github.com/apache/superset-showtime
6
+ Project-URL: Documentation, https://superset-showtime.readthedocs.io/
7
+ Project-URL: Repository, https://github.com/apache/superset-showtime.git
8
+ Project-URL: Bug Tracker, https://github.com/apache/superset-showtime/issues
9
+ Project-URL: Changelog, https://github.com/apache/superset-showtime/blob/main/CHANGELOG.md
10
+ Author-email: Maxime Beauchemin <maximebeauchemin@gmail.com>
11
+ License-Expression: Apache-2.0
12
+ Keywords: aws,circus,devops,environments,ephemeral,github,showtime,superset
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: Apache Software License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.8
21
+ Classifier: Programming Language :: Python :: 3.9
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Topic :: Software Development :: Build Tools
26
+ Classifier: Topic :: System :: Systems Administration
27
+ Requires-Python: >=3.8
28
+ Requires-Dist: boto3>=1.26.0
29
+ Requires-Dist: botocore>=1.29.0
30
+ Requires-Dist: gitpython>=3.1.0
31
+ Requires-Dist: httpx>=0.24.0
32
+ Requires-Dist: pydantic>=2.0.0
33
+ Requires-Dist: python-dateutil>=2.8.0
34
+ Requires-Dist: pyyaml>=6.0
35
+ Requires-Dist: rich>=10.0.0
36
+ Requires-Dist: typer>=0.9.0
37
+ Provides-Extra: all
38
+ Requires-Dist: azure-mgmt-containerinstance>=10.0.0; extra == 'all'
39
+ Requires-Dist: azure-storage-blob>=12.0.0; extra == 'all'
40
+ Requires-Dist: boto3>=1.26.0; extra == 'all'
41
+ Requires-Dist: botocore>=1.29.0; extra == 'all'
42
+ Requires-Dist: google-cloud-compute>=1.11.0; extra == 'all'
43
+ Requires-Dist: google-cloud-storage>=2.8.0; extra == 'all'
44
+ Requires-Dist: kubernetes>=26.0.0; extra == 'all'
45
+ Provides-Extra: aws
46
+ Requires-Dist: boto3>=1.26.0; extra == 'aws'
47
+ Requires-Dist: botocore>=1.29.0; extra == 'aws'
48
+ Provides-Extra: azure
49
+ Requires-Dist: azure-mgmt-containerinstance>=10.0.0; extra == 'azure'
50
+ Requires-Dist: azure-storage-blob>=12.0.0; extra == 'azure'
51
+ Provides-Extra: dev
52
+ Requires-Dist: build>=1.0.0; extra == 'dev'
53
+ Requires-Dist: mypy>=1.0.0; extra == 'dev'
54
+ Requires-Dist: pre-commit>=3.0.0; extra == 'dev'
55
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
56
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
57
+ Requires-Dist: ruff>=0.1.9; extra == 'dev'
58
+ Requires-Dist: twine>=4.0.0; extra == 'dev'
59
+ Provides-Extra: gcp
60
+ Requires-Dist: google-cloud-compute>=1.11.0; extra == 'gcp'
61
+ Requires-Dist: google-cloud-storage>=2.8.0; extra == 'gcp'
62
+ Provides-Extra: k8s
63
+ Requires-Dist: kubernetes>=26.0.0; extra == 'k8s'
64
+ Description-Content-Type: text/markdown
65
+
66
+ # ๐ŸŽช Superset Showtime
67
+
68
+ **Modern ephemeral environment management for Apache Superset using circus tent emoji labels**
69
+
70
+ [![PyPI version](https://badge.fury.io/py/superset-showtime.svg)](https://badge.fury.io/py/superset-showtime)
71
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
72
+
73
+ ## ๐ŸŽฏ What is Showtime?
74
+
75
+ Superset Showtime replaces the complex GitHub Actions scripts for ephemeral environments with a simple, powerful CLI tool that uses **circus tent emoji labels** for state management.
76
+
77
+ ### The Problem We Solve
78
+
79
+ **Current Superset ephemeral environment issues:**
80
+ - ๐Ÿšจ **Stale environments** - New commits don't update existing environments
81
+ - ๐Ÿ’ธ **Resource waste** - Multiple environments per PR, no automatic cleanup
82
+ - ๐Ÿ”ง **Hard to maintain** - Complex GitHub Actions logic scattered across workflows
83
+ - ๐Ÿ‘€ **Poor visibility** - Hard to see what environments exist and their status
84
+
85
+ ### The Showtime Solution
86
+
87
+ **๐ŸŽช GitHub labels become a visual state machine:**
88
+ ```bash
89
+ # User adds trigger label in GitHub UI:
90
+ ๐ŸŽช trigger-start
91
+
92
+ # System responds with state labels:
93
+ ๐ŸŽช abc123f ๐Ÿšฆ building # Environment abc123f is building
94
+ ๐ŸŽช ๐ŸŽฏ abc123f # abc123f is the active environment
95
+ ๐ŸŽช abc123f ๐Ÿ“… 2024-01-15T14-30 # Created timestamp
96
+ ๐ŸŽช abc123f โŒ› 24h # Time-to-live policy
97
+ ๐ŸŽช abc123f ๐Ÿคก maxime # Requested by maxime (clown emoji!)
98
+
99
+ # When ready:
100
+ ๐ŸŽช abc123f ๐Ÿšฆ running # Environment is now running
101
+ ๐ŸŽช abc123f ๐ŸŒ 52-1-2-3 # Available at http://52.1.2.3:8080
102
+ ```
103
+
104
+ ## ๐Ÿš€ Quick Start for Superset Contributors
105
+
106
+ ### 1. As a Contributor (Using GitHub Labels)
107
+
108
+ **Create an ephemeral environment:**
109
+ 1. Go to your PR in GitHub
110
+ 2. Add label: `๐ŸŽช trigger-start`
111
+ 3. Watch the magic happen - labels will update automatically
112
+ 4. When you see `๐ŸŽช ๐Ÿšฆ {sha} running`, your environment is ready!
113
+ 5. Get URL from `๐ŸŽช ๐ŸŒ {sha} {ip}` โ†’ `http://{ip}:8080`
114
+
115
+ **Configure your environment:**
116
+ ```bash
117
+ # Add these labels to enable Superset feature flags:
118
+ ๐ŸŽช conf-enable-ALERTS # Enable alerts feature
119
+ ๐ŸŽช conf-enable-DASHBOARD_RBAC # Enable dashboard RBAC
120
+ ๐ŸŽช conf-disable-SSH_TUNNELING # Disable SSH tunneling
121
+ ```
122
+
123
+ **Clean up when done:**
124
+ ```bash
125
+ # Add this label:
126
+ ๐ŸŽช trigger-stop
127
+ # All circus labels disappear, AWS resources cleaned up
128
+ ```
129
+
130
+ ### 2. As a Maintainer (Using CLI)
131
+
132
+ **Install the CLI:**
133
+ ```bash
134
+ pip install superset-showtime
135
+ export GITHUB_TOKEN=your_token
136
+ ```
137
+
138
+ **Monitor all environments:**
139
+ ```bash
140
+ showtime list # See all active environments
141
+ showtime status 1234 # Check specific PR environment
142
+ showtime labels # Learn the complete label system
143
+ ```
144
+
145
+ **Test and debug:**
146
+ ```bash
147
+ showtime start 1234 --dry-run-aws # Test environment creation
148
+ showtime test-lifecycle 1234 # Full workflow simulation
149
+ ```
150
+
151
+ ## ๐ŸŽช Complete Label Reference
152
+
153
+ ### ๐ŸŽฏ Trigger Labels (Add These to Your PR)
154
+
155
+ | Label | Action | Result |
156
+ |-------|---------|---------|
157
+ | `๐ŸŽช trigger-start` | Create environment | Builds and deploys ephemeral environment |
158
+ | `๐ŸŽช trigger-stop` | Destroy environment | Cleans up AWS resources and removes all labels |
159
+ | `๐ŸŽช trigger-sync` | Update to latest commit | Zero-downtime rolling update |
160
+ | `๐ŸŽช conf-enable-ALERTS` | Enable feature flag | Sets `SUPERSET_FEATURE_ALERTS=True` |
161
+ | `๐ŸŽช conf-disable-DASHBOARD_RBAC` | Disable feature flag | Sets `SUPERSET_FEATURE_DASHBOARD_RBAC=False` |
162
+
163
+ ### ๐Ÿ“Š State Labels (Automatically Managed)
164
+
165
+ | Label Pattern | Meaning | Example |
166
+ |---------------|---------|---------|
167
+ | `๐ŸŽช {sha} ๐Ÿšฆ {status}` | Environment status | `๐ŸŽช abc123f ๐Ÿšฆ running` |
168
+ | `๐ŸŽช ๐ŸŽฏ {sha}` | Active environment pointer | `๐ŸŽช ๐ŸŽฏ abc123f` |
169
+ | `๐ŸŽช ๐Ÿ—๏ธ {sha}` | Building environment pointer | `๐ŸŽช ๐Ÿ—๏ธ def456a` |
170
+ | `๐ŸŽช {sha} ๐Ÿ“… {timestamp}` | Creation time | `๐ŸŽช abc123f ๐Ÿ“… 2024-01-15T14-30` |
171
+ | `๐ŸŽช {sha} ๐ŸŒ {ip-with-dashes}` | Environment IP | `๐ŸŽช abc123f ๐ŸŒ 52-1-2-3` |
172
+ | `๐ŸŽช {sha} โŒ› {ttl}` | Time-to-live policy | `๐ŸŽช abc123f โŒ› 24h` |
173
+ | `๐ŸŽช {sha} ๐Ÿคก {username}` | Who requested | `๐ŸŽช abc123f ๐Ÿคก maxime` |
174
+ | `๐ŸŽช {sha} โš™๏ธ {config}` | Feature flags enabled | `๐ŸŽช abc123f โš™๏ธ alerts,debug` |
175
+
176
+ ## ๐Ÿ”„ Complete Workflows
177
+
178
+ ### Creating Your First Environment
179
+
180
+ 1. **Add trigger label** in GitHub UI: `๐ŸŽช trigger-start`
181
+ 2. **Watch state labels appear:**
182
+ ```
183
+ ๐ŸŽช abc123f ๐Ÿšฆ building โ† Environment is building
184
+ ๐ŸŽช ๐ŸŽฏ abc123f โ† This is the active environment
185
+ ๐ŸŽช abc123f ๐Ÿ“… 2024-01-15T14-30 โ† Started building at this time
186
+ ```
187
+ 3. **Wait for completion:**
188
+ ```
189
+ ๐ŸŽช abc123f ๐Ÿšฆ running โ† Now ready!
190
+ ๐ŸŽช abc123f ๐ŸŒ 52-1-2-3 โ† Visit http://52.1.2.3:8080
191
+ ```
192
+
193
+ ### Enabling Feature Flags
194
+
195
+ 1. **Add config label:** `๐ŸŽช conf-enable-ALERTS`
196
+ 2. **Watch config update:**
197
+ ```
198
+ ๐ŸŽช abc123f โš™๏ธ standard โ† Before
199
+ ๐ŸŽช abc123f โš™๏ธ alerts โ† After (feature enabled!)
200
+ ```
201
+
202
+ ### Rolling Updates (Automatic!)
203
+
204
+ When you push new commits, Showtime automatically:
205
+ 1. **Detects new commit** via GitHub webhook
206
+ 2. **Builds new environment** alongside old one
207
+ 3. **Switches traffic** when new environment is ready
208
+ 4. **Cleans up old environment**
209
+
210
+ You'll see:
211
+ ```bash
212
+ # During update:
213
+ ๐ŸŽช abc123f ๐Ÿšฆ running # Old environment still serving
214
+ ๐ŸŽช def456a ๐Ÿšฆ building # New environment building
215
+ ๐ŸŽช ๐ŸŽฏ abc123f # Traffic still on old
216
+ ๐ŸŽช ๐Ÿ—๏ธ def456a # New one being prepared
217
+
218
+ # After update:
219
+ ๐ŸŽช def456a ๐Ÿšฆ running # New environment live
220
+ ๐ŸŽช ๐ŸŽฏ def456a # Traffic switched
221
+ ๐ŸŽช def456a ๐ŸŒ 52-4-5-6 # New IP address
222
+ # All abc123f labels removed automatically
223
+ ```
224
+
225
+ ## ๐Ÿ”’ Security & Permissions
226
+
227
+ ### Who Can Use This?
228
+
229
+ - **โœ… Superset maintainers** (with write access) can add trigger labels
230
+ - **โŒ External contributors** cannot trigger environments (no write access to add labels)
231
+ - **๐Ÿ”’ Secure by design** - only trusted users can create expensive AWS resources
232
+
233
+ ### How GitHub Actions Work
234
+
235
+ The new system replaces complex GHA scripts with simple ones:
236
+
237
+ ```yaml
238
+ # .github/workflows/circus.yml (replaces current ephemeral-env.yml)
239
+ on:
240
+ pull_request_target:
241
+ types: [labeled, unlabeled, synchronize]
242
+
243
+ jobs:
244
+ circus-handler:
245
+ if: contains(github.event.label.name, '๐ŸŽช')
246
+ steps:
247
+ - name: Install Showtime from PyPI
248
+ run: pip install superset-showtime
249
+
250
+ - name: Process circus triggers
251
+ run: python -m showtime handle-trigger ${{ github.event.pull_request.number }}
252
+ ```
253
+
254
+ **Security benefits:**
255
+ - **Always runs trusted code** (from PyPI, not PR code)
256
+ - **Simple workflow logic** (just install CLI and run)
257
+ - **Same permission model** as current system
258
+
259
+ ## ๐Ÿ› ๏ธ Installation & Setup
260
+
261
+ ### For Contributors (GitHub Labels Only)
262
+ No installation needed! Just use the GitHub label system.
263
+
264
+ ### For Maintainers (CLI Access)
265
+
266
+ **Install CLI:**
267
+ ```bash
268
+ pip install superset-showtime
269
+ export GITHUB_TOKEN=your_personal_access_token
270
+ ```
271
+
272
+ **Test CLI:**
273
+ ```bash
274
+ showtime list # See all active environments
275
+ showtime status 1234 # Check specific environment
276
+ showtime labels # Learn complete label system
277
+ ```
278
+
279
+ ### For Repository Setup (One-Time)
280
+
281
+ **1. Install GitHub workflows:**
282
+ Copy `.github/workflows/circus.yml` and `.github/workflows/circus-cleanup.yml` to your Superset repo.
283
+
284
+ **2. Add repository secrets:**
285
+ - `AWS_ACCESS_KEY_ID` (already exists)
286
+ - `AWS_SECRET_ACCESS_KEY` (already exists)
287
+ - `GITHUB_TOKEN` (already exists)
288
+
289
+ **3. Replace old workflows:**
290
+ Remove or disable the current `ephemeral-env.yml` and `ephemeral-env-pr-close.yml`.
291
+
292
+ ## ๐Ÿ“Š CLI Commands Reference
293
+
294
+ ### Core Commands
295
+ ```bash
296
+ showtime start 1234 # Create environment (with dry-run options)
297
+ showtime stop 1234 # Delete environment
298
+ showtime status 1234 # Show environment status
299
+ showtime list # List all environments across org
300
+ showtime labels # Complete label reference guide
301
+ ```
302
+
303
+ ### Testing & Development
304
+ ```bash
305
+ showtime start 1234 --dry-run-aws # Mock AWS, real GitHub labels
306
+ showtime test-lifecycle 1234 --real-github # Full workflow simulation
307
+ showtime handle-trigger 1234 --dry-run-aws # Simulate GitHub Actions
308
+ ```
309
+
310
+ ### Advanced Operations
311
+ ```bash
312
+ showtime cleanup --older-than 48h # Clean up old environments
313
+ showtime list --status running --user maxime # Filter environments
314
+ ```
315
+
316
+ ## ๐ŸŽช Benefits for Superset
317
+
318
+ ### For Contributors
319
+ - **๐ŸŽฏ Simple workflow** - Just add/remove GitHub labels
320
+ - **๐Ÿ‘€ Visual feedback** - See environment status in PR labels
321
+ - **โšก Automatic updates** - New commits update environments automatically
322
+ - **๐Ÿ”ง Live configuration** - Enable/disable feature flags without rebuilding
323
+
324
+ ### For Maintainers
325
+ - **๐Ÿ“Š Complete visibility** - `showtime list` shows all environments
326
+ - **๐Ÿงน Easy cleanup** - Automatic expired environment cleanup
327
+ - **๐Ÿ” Better debugging** - Clear state in labels, comprehensive CLI
328
+ - **๐Ÿ’ฐ Cost savings** - No duplicate environments, proper cleanup
329
+
330
+ ### For Operations
331
+ - **๐Ÿ“ Simpler workflows** - Replace complex GHA scripts with simple CLI calls
332
+ - **๐Ÿ”’ Same security model** - No new permissions needed
333
+ - **๐ŸŽฏ Deterministic** - Predictable AWS resource naming
334
+ - **๐Ÿšจ Monitoring ready** - 48h maximum lifetime, scheduled cleanup
335
+
336
+ ## ๐Ÿ—๏ธ Architecture
337
+
338
+ ### State Management
339
+ All state lives in **GitHub labels** - no external databases needed:
340
+ - **Trigger labels** (`๐ŸŽช trigger-*`) - Commands that get processed and removed
341
+ - **State labels** (`๐ŸŽช ๐Ÿšฆ *`) - Current environment status, managed by CLI
342
+
343
+ ### AWS Resources
344
+ Deterministic naming enables reliable cleanup:
345
+ - **ECS Service:** `pr-{pr_number}-{sha}` (e.g., `pr-1234-abc123f`)
346
+ - **ECR Image:** `pr-{pr_number}-{sha}-ci` (e.g., `pr-1234-abc123f-ci`)
347
+
348
+ ### Rolling Updates
349
+ Zero-downtime updates by running multiple environments:
350
+ 1. Keep old environment serving traffic
351
+ 2. Build new environment in parallel
352
+ 3. Switch traffic when new environment is healthy
353
+ 4. Clean up old environment
354
+
355
+ ## ๐Ÿค Contributing
356
+
357
+ ### Testing Your Changes
358
+
359
+ **Test with real PRs safely:**
360
+ ```bash
361
+ # Test label management without AWS costs:
362
+ showtime start YOUR_PR_NUMBER --dry-run-aws --aws-sleep 10
363
+
364
+ # Test full lifecycle:
365
+ showtime test-lifecycle YOUR_PR_NUMBER --real-github
366
+ ```
367
+
368
+ ### Development Setup
369
+
370
+ ```bash
371
+ git clone https://github.com/mistercrunch/superset-showtime
372
+ cd superset-showtime
373
+
374
+ # Using uv (recommended):
375
+ uv pip install -e ".[dev]"
376
+ make pre-commit
377
+ make test
378
+
379
+ # Traditional pip:
380
+ pip install -e ".[dev]"
381
+ pre-commit install
382
+ pytest
383
+ ```
384
+
385
+ ## ๐Ÿ“„ License
386
+
387
+ Apache License 2.0 - same as Apache Superset.
388
+
389
+ ---
390
+
391
+ **๐ŸŽช "Ladies and gentlemen, welcome to Superset Showtime - where ephemeral environments are always under the big top!"** ๐ŸŽช๐Ÿคกโœจ
@@ -0,0 +1,16 @@
1
+ showtime/__init__.py,sha256=vyBFCZ0eKSRhMQAI7O2Qkwiv7-eAkiSjkg6RHIBIU1U,482
2
+ showtime/__main__.py,sha256=EVaDaTX69yIhCzChg99vqvFSCN4ELstEt7Mpb9FMZX8,109
3
+ showtime/cli.py,sha256=CXV3HNkwijdUla92txSy6XVV-M925PxVK6jIfMzIsQc,54440
4
+ showtime/commands/__init__.py,sha256=M2wn5hYgwNCryMjLT79ncobvK884r-xk3znkCmINN_0,28
5
+ showtime/commands/start.py,sha256=DPGbgvGPh7I60LK_VioDljUhdmhNFVjEy6BchFv1lCo,1026
6
+ showtime/core/__init__.py,sha256=54hbdFNGrzuNMBdraezfjT8Zi6g221pKlJ9mREnKwCw,34
7
+ showtime/core/aws.py,sha256=GXJc3h55J5n5czjqkJpWLhvLMCdcZjZaOdVSsdvu7PQ,30008
8
+ showtime/core/circus.py,sha256=avvcFqvHRKo2o_Yxm0V1jVRMF2RSLABTqhe_nMO3GAM,9504
9
+ showtime/core/config.py,sha256=jISjWE79vFaIX1nqeNhRfs_jM5Xoe-p7LTmJzT1OsSI,4348
10
+ showtime/core/emojis.py,sha256=iq2EginrCKN_oMh3XmZF9SgbMNSXV6e-JgwUAtRwUuM,2430
11
+ showtime/core/github.py,sha256=U8H9tudJjmHVr1aWKDd7o5AJkmgUnX_AIC2lqh4voTg,7967
12
+ showtime/data/ecs-task-definition.json,sha256=r1cgkZJ2SS1gul-WFuV-QlzdFjr5n3g-kdKka79N-Jk,1687
13
+ superset_showtime-0.1.0.dist-info/METADATA,sha256=_gobO4ZLHlyTeyOrwhzSBgxLIHcElL6M0p8mc0Pb4Zs,13938
14
+ superset_showtime-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
+ superset_showtime-0.1.0.dist-info/entry_points.txt,sha256=rDW7oZ57mqyBUS4N_3_R7bZNGVHB-104jwmY-hHC_ck,85
16
+ superset_showtime-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ showtime = showtime.cli:main
3
+ superset-showtime = showtime.cli:main