superset-showtime 0.2.8__tar.gz → 0.2.9__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.

Potentially problematic release.


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

Files changed (31) hide show
  1. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/.claude/settings.local.json +5 -1
  2. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/CLAUDE.md +67 -36
  3. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/PKG-INFO +42 -115
  4. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/README.md +41 -114
  5. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/showtime/__init__.py +1 -1
  6. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/showtime/cli.py +395 -56
  7. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/showtime/core/circus.py +11 -1
  8. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/showtime/core/github_messages.py +11 -1
  9. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/workflows-reference/showtime-trigger.yml +24 -55
  10. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/.gitignore +0 -0
  11. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/.pre-commit-config.yaml +0 -0
  12. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/Makefile +0 -0
  13. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/dev-setup.sh +0 -0
  14. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/pypi-push.sh +0 -0
  15. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/pyproject.toml +0 -0
  16. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/requirements-dev.txt +0 -0
  17. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/requirements.txt +0 -0
  18. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/showtime/__main__.py +0 -0
  19. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/showtime/commands/__init__.py +0 -0
  20. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/showtime/commands/start.py +0 -0
  21. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/showtime/core/__init__.py +0 -0
  22. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/showtime/core/aws.py +0 -0
  23. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/showtime/core/emojis.py +0 -0
  24. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/showtime/core/github.py +0 -0
  25. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/showtime/core/label_colors.py +0 -0
  26. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/showtime/data/ecs-task-definition.json +0 -0
  27. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/tests/__init__.py +0 -0
  28. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/tests/unit/__init__.py +0 -0
  29. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/tests/unit/test_circus.py +0 -0
  30. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/uv.lock +0 -0
  31. {superset_showtime-0.2.8 → superset_showtime-0.2.9}/workflows-reference/showtime-cleanup.yml +0 -0
@@ -30,7 +30,11 @@
30
30
  "Bash(git grep:*)",
31
31
  "Bash(git push:*)",
32
32
  "Bash(make lint:*)",
33
- "Bash(mypy:*)"
33
+ "Bash(mypy:*)",
34
+ "Bash(sed:*)",
35
+ "Bash(git pull:*)",
36
+ "Bash(git rebase:*)",
37
+ "Bash(git fetch:*)"
34
38
  ],
35
39
  "deny": [],
36
40
  "ask": []
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
4
4
 
5
5
  ## Project Overview
6
6
 
7
- Superset Showtime is a Python CLI tool for managing Apache Superset ephemeral environments using **circus tent emoji labels** as a state management system on GitHub PRs. The system replaces complex GitHub Actions scripts with a simple CLI that uses GitHub labels for ACID-like state transactions.
7
+ Superset Showtime is a Python CLI tool for managing Apache Superset ephemeral environments using **circus tent emoji labels** as a state management system on GitHub PRs. The system implements **ACID-style atomic transactions** using GitHub labels as a distributed coordination mechanism, with **compare-and-swap** patterns to prevent race conditions.
8
8
 
9
9
  ## Development Commands
10
10
 
@@ -22,11 +22,11 @@ python -m showtime labels # Complete label reference
22
22
  python -m showtime list # List all environments (requires GITHUB_TOKEN)
23
23
  python -m showtime test-lifecycle 1234 # Full workflow simulation
24
24
 
25
- # Test with real GitHub PRs safely
25
+ # Test new unified sync command
26
26
  export GITHUB_TOKEN=xxx
27
- showtime start 34809 --dry-run-aws --aws-sleep 5 # Mock AWS, real GitHub labels
28
- showtime status 34809 # Check environment status
29
- showtime stop 34809 --force # Clean up test labels
27
+ showtime sync 34809 --check-only # Analyze what's needed
28
+ showtime sync 34809 --dry-run-aws --dry-run-docker # Test full flow safely
29
+ showtime set-state building 34809 # Manual state transitions
30
30
  ```
31
31
 
32
32
  ### Testing Single Components
@@ -39,10 +39,19 @@ python -c "from showtime.core.circus import Show; show = Show(pr_number=1234, sh
39
39
  ## Architecture Overview
40
40
 
41
41
  ### Core State Management Pattern
42
- The system uses **GitHub labels as a distributed state machine**:
43
- - **Trigger labels** (`🎪 trigger-start`) - Commands added by users, processed and removed by CLI
44
- - **State labels** (`🎪 🚦 abc123f running`) - Per-SHA status managed by CLI
42
+ The system uses **GitHub labels as a distributed ACID-style database**:
43
+ - **Trigger labels** (`🎪 ⚡ showtime-trigger-start`) - Commands added by users, atomically processed and removed
44
+ - **State labels** (`🎪 abc123f 🚦 building`) - Per-SHA status managed with atomic compare-and-swap operations
45
45
  - **No external database** - All state reconstructed from GitHub labels
46
+ - **Race condition prevention** - Atomic claim prevents double-processing of triggers
47
+
48
+ ### Enhanced State Lifecycle
49
+ **Complete state progression**: `building → built → deploying → running/failed`
50
+ - **building**: Docker image construction in progress
51
+ - **built**: Docker build complete, ready for AWS deployment
52
+ - **deploying**: AWS ECS deployment in progress
53
+ - **running**: Environment live and accessible
54
+ - **failed**: Build or deployment failed
46
55
 
47
56
  ### Key Classes and Responsibilities
48
57
 
@@ -96,8 +105,9 @@ Replicates current GitHub Actions AWS logic:
96
105
  - **Security model**: `pull_request_target` + PyPI install (no PR code execution)
97
106
 
98
107
  #### CLI Commands Called by GHA
99
- - `showtime handle-trigger {pr-number}` - Process trigger labels
100
- - `showtime handle-sync {pr-number}` - Handle new commits
108
+ - `showtime sync {pr-number} --check-only` - Analyze state and determine build_needed
109
+ - `showtime sync {pr-number} --sha {sha}` - Execute atomic claim + build + deploy
110
+ - `showtime set-state {state} {pr-number}` - Manual state transitions
101
111
  - `showtime cleanup --older-than 48h` - Scheduled cleanup
102
112
 
103
113
  ## Important Implementation Details
@@ -114,15 +124,23 @@ The CLI has comprehensive dry-run support for safe testing:
114
124
  ```bash
115
125
  --dry-run-aws # Skip AWS operations, use mock data
116
126
  --dry-run-github # Skip GitHub operations, show what would happen
127
+ --dry-run-docker # Skip Docker build, use mock success
117
128
  --aws-sleep N # Sleep N seconds to simulate AWS timing
118
129
  ```
119
130
 
120
- ### AWS Resource Naming
121
- Must maintain compatibility with existing Superset infrastructure:
131
+ ### AWS Resource Naming & Docker Integration
132
+ **Direct Docker build** (no supersetbot dependency):
133
+ - **Docker Image**: `apache/superset:pr-{pr_number}-{sha}-ci` (single tag, built directly)
122
134
  - **ECS Service**: `pr-{pr_number}-{sha}` (e.g., `pr-1234-abc123f`)
123
- - **ECR Image**: `pr-{pr_number}-{sha}-ci`
124
135
  - **Network**: Same subnets/security groups as current production setup
125
136
 
137
+ **Docker Build Command**:
138
+ ```bash
139
+ docker buildx build --push --load --platform linux/amd64 --target ci \
140
+ --build-arg INCLUDE_CHROMIUM=false --build-arg LOAD_EXAMPLES_DUCKDB=true \
141
+ -t apache/superset:pr-{pr}-{sha}-ci .
142
+ ```
143
+
126
144
  ### State Recovery Pattern
127
145
  Since the CLI is stateless, it always reconstructs state from GitHub labels:
128
146
  ```python
@@ -133,15 +151,22 @@ show = pr.current_show # Finds active environment
133
151
 
134
152
  ## Critical Development Notes
135
153
 
136
- ### Label Operations Must Be Atomic
137
- GitHub label operations are the "database transactions" - handle carefully:
154
+ ### ACID-Style Atomic Transactions
155
+ GitHub label operations implement compare-and-swap pattern for race condition prevention:
138
156
  ```python
139
- # Remove trigger immediately after processing
140
- github.remove_label(pr_number, trigger_label)
141
- # Update state labels atomically
142
- github.remove_circus_labels(pr_number)
143
- for label in new_labels:
144
- github.add_label(pr_number, label)
157
+ # Atomic claim pattern in _atomic_claim_environment():
158
+ # 1. CHECK: Validate current state allows new work
159
+ # 2. COMPARE: Ensure triggers exist and state is valid
160
+ # 3. SWAP: Remove triggers + Set building state atomically
161
+ # 4. COMMIT: Environment successfully claimed
162
+
163
+ # Example atomic transaction:
164
+ if not _validate_non_active_state(pr):
165
+ return False # Another job already active
166
+ github.remove_label(pr_number, trigger_label) # Remove trigger
167
+ github.remove_circus_labels(pr_number) # Clear stale state
168
+ for label in building_show.to_circus_labels():
169
+ github.add_label(pr_number, label) # Set building state
145
170
  ```
146
171
 
147
172
  ### Per-SHA Format Required
@@ -166,16 +191,14 @@ showtime stop 34809 --force
166
191
 
167
192
  ## Current Implementation Status
168
193
 
169
- ### ✅ Fully Implemented (Production Ready)
170
- - **Blue-green deployment**: Zero-downtime updates with health checks
171
- - **AWS integration**: Complete ECS/ECR operations with DockerHub pulling
172
- - **Smart sync system**: Intelligent PR state detection and auto-sync
173
- - **GitHub Actions workflows**: Drop-in replacement for ephemeral-env.yml
174
- - **TTL-based cleanup**: Respects individual environment preferences
175
- - **SHA override support**: Deploy any specific commit for testing
176
- - **Freeze functionality**: Pin environments during testing
177
- - **Enhanced CLI**: Clickable links, full-width tables, real-time progress
178
- - **Unified label system**: Searchable namespaced labels with color themes
194
+ ### ✅ Current Architecture (Production Ready)
195
+ - **ACID-style transactions**: Atomic compare-and-swap prevents race conditions
196
+ - **Direct Docker integration**: No supersetbot dependency, single tag builds
197
+ - **Streamlined GitHub Actions**: 3-step workflow (check setup sync)
198
+ - **Enhanced state machine**: building→built→deploying→running/failed lifecycle
199
+ - **Unified sync command**: Handles atomic claim + build + deploy in one command
200
+ - **Smart conditionals**: Skip Docker setup when build_needed=false
201
+ - **Race condition safe**: Multiple jobs can't double-process triggers
179
202
 
180
203
  ### 🎯 Label System (Streamlined)
181
204
  - **Trigger labels**: `🎪 ⚡ showtime-trigger-start` (namespaced, searchable)
@@ -183,8 +206,16 @@ showtime stop 34809 --force
183
206
  - **Freeze support**: `🎪 🧊 showtime-freeze` (prevents auto-sync)
184
207
  - **Automatic creation**: Labels get proper colors/descriptions automatically
185
208
 
186
- ### 📦 Ready for Deployment
187
- - **GitHub Actions**: `workflows-reference/showtime-trigger.yml` + `showtime-cleanup.yml`
188
- - **PyPI package**: Built with dependencies, ready for `pip install superset-showtime`
189
- - **Testing infrastructure**: Comprehensive dry-run and manual testing support
190
- - **Documentation**: Complete README with workflows and examples
209
+ ### 📦 Key Commands for Development
210
+ ```bash
211
+ # Core sync command (replaces handle-trigger, handle-sync):
212
+ showtime sync PR_NUMBER --check-only # Returns build_needed + target_sha
213
+ showtime sync PR_NUMBER --sha SHA # Atomic claim + build + deploy
214
+
215
+ # Manual state management:
216
+ showtime set-state building PR_NUMBER # Set specific state
217
+ showtime set-state failed PR_NUMBER --error-msg "Build failed"
218
+
219
+ # Development testing:
220
+ showtime sync PR_NUMBER --dry-run-aws --dry-run-docker --dry-run-github
221
+ ```
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: superset-showtime
3
- Version: 0.2.8
3
+ Version: 0.2.9
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/
@@ -113,9 +113,34 @@ Superset Showtime is a CLI tool designed primarily for **GitHub Actions** to man
113
113
  🎪 abc123f 🌐 52-1-2-3 # Available at http://52.1.2.3:8080
114
114
  ```
115
115
 
116
- ## 📊 For Maintainers (CLI Operations)
116
+ ### 🔄 Showtime Workflow
117
+
118
+ ```mermaid
119
+ flowchart TD
120
+ A[User adds 🎪 ⚡ trigger-start] --> B[GitHub Actions: sync]
121
+ B --> C{Current state?}
122
+
123
+ C -->|No environment| D[🔒 Claim: Remove trigger + Set building]
124
+ C -->|Running + new SHA| E[🔒 Claim: Remove trigger + Set building]
125
+ C -->|Already building| F[❌ Exit: Another job active]
126
+ C -->|No triggers| G[❌ Exit: Nothing to do]
127
+
128
+ D --> H[📋 State: building]
129
+ E --> H
130
+ H --> I[🐳 Docker build]
131
+ I -->|Success| J[📋 State: built]
132
+ I -->|Fail| K[📋 State: failed]
133
+
134
+ J --> L[📋 State: deploying]
135
+ L --> M[☁️ AWS Deploy]
136
+ M -->|Success| N[📋 State: running]
137
+ M -->|Fail| O[📋 State: failed]
138
+
139
+ N --> P[🎪 Environment ready!]
140
+
141
+ Q[User adds 🎪 🛑 trigger-stop] --> R[🧹 Cleanup AWS + Remove labels]
142
+ ```
117
143
 
118
- > **Note**: CLI is mainly for debugging or developing Showtime itself. Primary interface is GitHub labels above.
119
144
 
120
145
  **Install CLI for debugging:**
121
146
  ```bash
@@ -229,129 +254,31 @@ You'll see:
229
254
 
230
255
  ### GitHub Actions Integration
231
256
 
232
- Showtime is designed to be called by Superset's GitHub Actions workflows:
233
-
234
- ```yaml
235
- # .github/workflows/showtime.yml - Integrates with Superset's existing build workflows
236
- on:
237
- pull_request_target:
238
- types: [labeled, unlabeled, synchronize]
239
-
240
- jobs:
241
- showtime-handler:
242
- if: contains(github.event.label.name, '🎪')
243
- steps:
244
- - name: Install Showtime from PyPI
245
- run: pip install superset-showtime
246
-
247
- - name: Process circus triggers
248
- run: python -m showtime handle-trigger ${{ github.event.pull_request.number }}
249
- ```
250
-
251
- **Integration approach:**
252
- - **Coordinates with Superset builds** - Uses existing container build workflows
253
- - **Runs trusted code** (from PyPI, not PR code)
254
- - **Simple orchestration logic** (install CLI and run commands)
255
- - **Leverages existing infrastructure** - Same AWS resources and permissions
256
-
257
- ## 🛠️ Installation & Setup
258
-
259
- ### For Contributors (GitHub Labels Only)
260
- No installation needed! Just use GitHub labels to trigger environments.
261
-
262
- ### For Maintainers (Manual CLI Operations)
257
+ **🎯 Live Workflow**: [showtime-trigger.yml](https://github.com/apache/superset/actions/workflows/showtime-trigger.yml)
263
258
 
264
- **Install CLI for debugging/testing:**
265
- ```bash
266
- pip install superset-showtime
267
- export GITHUB_TOKEN=your_personal_access_token
268
- ```
259
+ **How it works:**
260
+ - Triggers on PR label changes, commits, and closures
261
+ - Installs `superset-showtime` from PyPI (trusted code, not PR code)
262
+ - Runs `showtime sync` to handle trigger processing and deployments
263
+ - Supports manual testing via `workflow_dispatch` with specific SHA override
269
264
 
270
- **Manual operations:**
265
+ **Commands used:**
271
266
  ```bash
272
- showtime list # Monitor all active environments
273
- showtime status 1234 # Debug specific environment
274
- showtime labels # Reference complete label system
267
+ showtime sync PR_NUMBER --check-only # Determine build_needed + target_sha
268
+ showtime sync PR_NUMBER --sha SHA # Execute atomic claim + build + deploy
275
269
  ```
276
270
 
277
- ### For Repository Integration (GitHub Actions)
278
-
279
- **1. Install GitHub workflows:**
280
- Copy `workflows-reference/showtime-trigger.yml` and `workflows-reference/showtime-cleanup.yml` to Superset's `.github/workflows/`.
281
-
282
- **2. Configure secrets (already exist in Superset):**
283
- - `AWS_ACCESS_KEY_ID`
284
- - `AWS_SECRET_ACCESS_KEY`
285
- - `GITHUB_TOKEN`
286
-
287
- **3. Dependencies:**
288
- Showtime coordinates with Superset's existing build infrastructure - no additional setup needed.
289
-
290
- ## 📊 CLI Reference (For Development/Debugging)
271
+ ## 🛠️ CLI Usage
291
272
 
292
- > **Primary Interface**: Use GitHub labels in PR interface. CLI is mainly for maintainers debugging or developing Showtime itself.
273
+ The CLI is primarily used by GitHub Actions, but available for debugging and advanced users:
293
274
 
294
- ### Debugging Commands
295
275
  ```bash
296
- showtime list # Monitor all environments
297
- showtime status 1234 # Debug specific environment
298
- showtime labels # Complete label reference
299
- showtime test-lifecycle 1234 # Full workflow simulation
300
- ```
301
-
302
- ### Manual Operations (Advanced)
303
- ```bash
304
- showtime start 1234 # Manually create environment
305
- showtime start 1234 --sha abc123f # Create environment (specific SHA)
306
- showtime stop 1234 # Manually delete environment
307
- showtime sync 1234 # Force sync to desired state
308
- showtime cleanup --respect-ttl # Manual cleanup
276
+ pip install superset-showtime
277
+ export GITHUB_TOKEN=your_token
278
+ showtime --help # See all available commands
309
279
  ```
310
280
 
311
- ### GitHub Actions Commands
312
- ```bash
313
- showtime handle-trigger 1234 # Process trigger labels (called by GHA)
314
- showtime cleanup --older-than 48h # Scheduled cleanup (called by GHA)
315
- ```
316
281
 
317
- ## 🎪 Benefits for Superset
318
-
319
- ### For Contributors
320
- - **🎯 Simple workflow** - Just add/remove GitHub labels
321
- - **👀 Visual feedback** - See environment status in PR labels
322
- - **⚡ Automatic updates** - New commits update environments automatically
323
- - **🔧 Configuration testing** - Test config changes through code commits
324
-
325
- ### For Maintainers
326
- - **📊 Complete visibility** - `showtime list` shows all environments
327
- - **🧹 Easy cleanup** - Automatic expired environment cleanup
328
- - **🔍 Better debugging** - Clear state in labels, comprehensive CLI
329
- - **💰 Cost savings** - No duplicate environments, proper cleanup
330
-
331
- ### For Operations
332
- - **📝 Simpler workflows** - Replace complex GHA scripts with simple CLI calls
333
- - **🔒 Same security model** - No new permissions needed
334
- - **🎯 Deterministic** - Predictable AWS resource naming
335
- - **🚨 Monitoring ready** - 48h maximum lifetime, scheduled cleanup
336
-
337
- ## 🏗️ Architecture
338
-
339
- ### State Management
340
- All state lives in **GitHub labels** - no external databases needed:
341
- - **Trigger labels** (`🎪 trigger-*`) - Commands that get processed and removed
342
- - **State labels** (`🎪 🚦 *`) - Current environment status, managed by CLI
343
-
344
- ### AWS Resources
345
- Deterministic naming enables reliable cleanup:
346
- - **ECS Service:** `pr-{pr_number}-{sha}` (e.g., `pr-1234-abc123f`)
347
- - **ECR Image:** `pr-{pr_number}-{sha}-ci` (e.g., `pr-1234-abc123f-ci`)
348
-
349
- ### Rolling Updates
350
- Zero-downtime updates by running multiple environments:
351
- 1. Keep old environment serving traffic
352
- 2. Build new environment in parallel
353
- 3. Switch traffic when new environment is healthy
354
- 4. Clean up old environment
355
282
 
356
283
  ## 🤝 Contributing
357
284
 
@@ -48,9 +48,34 @@ Superset Showtime is a CLI tool designed primarily for **GitHub Actions** to man
48
48
  🎪 abc123f 🌐 52-1-2-3 # Available at http://52.1.2.3:8080
49
49
  ```
50
50
 
51
- ## 📊 For Maintainers (CLI Operations)
51
+ ### 🔄 Showtime Workflow
52
+
53
+ ```mermaid
54
+ flowchart TD
55
+ A[User adds 🎪 ⚡ trigger-start] --> B[GitHub Actions: sync]
56
+ B --> C{Current state?}
57
+
58
+ C -->|No environment| D[🔒 Claim: Remove trigger + Set building]
59
+ C -->|Running + new SHA| E[🔒 Claim: Remove trigger + Set building]
60
+ C -->|Already building| F[❌ Exit: Another job active]
61
+ C -->|No triggers| G[❌ Exit: Nothing to do]
62
+
63
+ D --> H[📋 State: building]
64
+ E --> H
65
+ H --> I[🐳 Docker build]
66
+ I -->|Success| J[📋 State: built]
67
+ I -->|Fail| K[📋 State: failed]
68
+
69
+ J --> L[📋 State: deploying]
70
+ L --> M[☁️ AWS Deploy]
71
+ M -->|Success| N[📋 State: running]
72
+ M -->|Fail| O[📋 State: failed]
73
+
74
+ N --> P[🎪 Environment ready!]
75
+
76
+ Q[User adds 🎪 🛑 trigger-stop] --> R[🧹 Cleanup AWS + Remove labels]
77
+ ```
52
78
 
53
- > **Note**: CLI is mainly for debugging or developing Showtime itself. Primary interface is GitHub labels above.
54
79
 
55
80
  **Install CLI for debugging:**
56
81
  ```bash
@@ -164,129 +189,31 @@ You'll see:
164
189
 
165
190
  ### GitHub Actions Integration
166
191
 
167
- Showtime is designed to be called by Superset's GitHub Actions workflows:
168
-
169
- ```yaml
170
- # .github/workflows/showtime.yml - Integrates with Superset's existing build workflows
171
- on:
172
- pull_request_target:
173
- types: [labeled, unlabeled, synchronize]
174
-
175
- jobs:
176
- showtime-handler:
177
- if: contains(github.event.label.name, '🎪')
178
- steps:
179
- - name: Install Showtime from PyPI
180
- run: pip install superset-showtime
181
-
182
- - name: Process circus triggers
183
- run: python -m showtime handle-trigger ${{ github.event.pull_request.number }}
184
- ```
185
-
186
- **Integration approach:**
187
- - **Coordinates with Superset builds** - Uses existing container build workflows
188
- - **Runs trusted code** (from PyPI, not PR code)
189
- - **Simple orchestration logic** (install CLI and run commands)
190
- - **Leverages existing infrastructure** - Same AWS resources and permissions
191
-
192
- ## 🛠️ Installation & Setup
193
-
194
- ### For Contributors (GitHub Labels Only)
195
- No installation needed! Just use GitHub labels to trigger environments.
196
-
197
- ### For Maintainers (Manual CLI Operations)
192
+ **🎯 Live Workflow**: [showtime-trigger.yml](https://github.com/apache/superset/actions/workflows/showtime-trigger.yml)
198
193
 
199
- **Install CLI for debugging/testing:**
200
- ```bash
201
- pip install superset-showtime
202
- export GITHUB_TOKEN=your_personal_access_token
203
- ```
194
+ **How it works:**
195
+ - Triggers on PR label changes, commits, and closures
196
+ - Installs `superset-showtime` from PyPI (trusted code, not PR code)
197
+ - Runs `showtime sync` to handle trigger processing and deployments
198
+ - Supports manual testing via `workflow_dispatch` with specific SHA override
204
199
 
205
- **Manual operations:**
200
+ **Commands used:**
206
201
  ```bash
207
- showtime list # Monitor all active environments
208
- showtime status 1234 # Debug specific environment
209
- showtime labels # Reference complete label system
202
+ showtime sync PR_NUMBER --check-only # Determine build_needed + target_sha
203
+ showtime sync PR_NUMBER --sha SHA # Execute atomic claim + build + deploy
210
204
  ```
211
205
 
212
- ### For Repository Integration (GitHub Actions)
213
-
214
- **1. Install GitHub workflows:**
215
- Copy `workflows-reference/showtime-trigger.yml` and `workflows-reference/showtime-cleanup.yml` to Superset's `.github/workflows/`.
216
-
217
- **2. Configure secrets (already exist in Superset):**
218
- - `AWS_ACCESS_KEY_ID`
219
- - `AWS_SECRET_ACCESS_KEY`
220
- - `GITHUB_TOKEN`
221
-
222
- **3. Dependencies:**
223
- Showtime coordinates with Superset's existing build infrastructure - no additional setup needed.
224
-
225
- ## 📊 CLI Reference (For Development/Debugging)
206
+ ## 🛠️ CLI Usage
226
207
 
227
- > **Primary Interface**: Use GitHub labels in PR interface. CLI is mainly for maintainers debugging or developing Showtime itself.
208
+ The CLI is primarily used by GitHub Actions, but available for debugging and advanced users:
228
209
 
229
- ### Debugging Commands
230
210
  ```bash
231
- showtime list # Monitor all environments
232
- showtime status 1234 # Debug specific environment
233
- showtime labels # Complete label reference
234
- showtime test-lifecycle 1234 # Full workflow simulation
235
- ```
236
-
237
- ### Manual Operations (Advanced)
238
- ```bash
239
- showtime start 1234 # Manually create environment
240
- showtime start 1234 --sha abc123f # Create environment (specific SHA)
241
- showtime stop 1234 # Manually delete environment
242
- showtime sync 1234 # Force sync to desired state
243
- showtime cleanup --respect-ttl # Manual cleanup
211
+ pip install superset-showtime
212
+ export GITHUB_TOKEN=your_token
213
+ showtime --help # See all available commands
244
214
  ```
245
215
 
246
- ### GitHub Actions Commands
247
- ```bash
248
- showtime handle-trigger 1234 # Process trigger labels (called by GHA)
249
- showtime cleanup --older-than 48h # Scheduled cleanup (called by GHA)
250
- ```
251
216
 
252
- ## 🎪 Benefits for Superset
253
-
254
- ### For Contributors
255
- - **🎯 Simple workflow** - Just add/remove GitHub labels
256
- - **👀 Visual feedback** - See environment status in PR labels
257
- - **⚡ Automatic updates** - New commits update environments automatically
258
- - **🔧 Configuration testing** - Test config changes through code commits
259
-
260
- ### For Maintainers
261
- - **📊 Complete visibility** - `showtime list` shows all environments
262
- - **🧹 Easy cleanup** - Automatic expired environment cleanup
263
- - **🔍 Better debugging** - Clear state in labels, comprehensive CLI
264
- - **💰 Cost savings** - No duplicate environments, proper cleanup
265
-
266
- ### For Operations
267
- - **📝 Simpler workflows** - Replace complex GHA scripts with simple CLI calls
268
- - **🔒 Same security model** - No new permissions needed
269
- - **🎯 Deterministic** - Predictable AWS resource naming
270
- - **🚨 Monitoring ready** - 48h maximum lifetime, scheduled cleanup
271
-
272
- ## 🏗️ Architecture
273
-
274
- ### State Management
275
- All state lives in **GitHub labels** - no external databases needed:
276
- - **Trigger labels** (`🎪 trigger-*`) - Commands that get processed and removed
277
- - **State labels** (`🎪 🚦 *`) - Current environment status, managed by CLI
278
-
279
- ### AWS Resources
280
- Deterministic naming enables reliable cleanup:
281
- - **ECS Service:** `pr-{pr_number}-{sha}` (e.g., `pr-1234-abc123f`)
282
- - **ECR Image:** `pr-{pr_number}-{sha}-ci` (e.g., `pr-1234-abc123f-ci`)
283
-
284
- ### Rolling Updates
285
- Zero-downtime updates by running multiple environments:
286
- 1. Keep old environment serving traffic
287
- 2. Build new environment in parallel
288
- 3. Switch traffic when new environment is healthy
289
- 4. Clean up old environment
290
217
 
291
218
  ## 🤝 Contributing
292
219
 
@@ -4,7 +4,7 @@
4
4
  Circus tent emoji state tracking for Apache Superset ephemeral environments.
5
5
  """
6
6
 
7
- __version__ = "0.2.8"
7
+ __version__ = "0.2.9"
8
8
  __author__ = "Maxime Beauchemin"
9
9
  __email__ = "maximebeauchemin@gmail.com"
10
10
 
@@ -4,6 +4,7 @@
4
4
  Main command-line interface for Apache Superset circus tent environment management.
5
5
  """
6
6
 
7
+ import subprocess
7
8
  from typing import Optional
8
9
 
9
10
  import typer
@@ -14,6 +15,8 @@ from .core.circus import PullRequest, Show, short_sha
14
15
  from .core.emojis import STATUS_DISPLAY
15
16
  from .core.github import GitHubError, GitHubInterface
16
17
  from .core.github_messages import (
18
+ building_comment,
19
+ failure_comment,
17
20
  get_aws_console_urls,
18
21
  rolling_failure_comment,
19
22
  rolling_start_comment,
@@ -176,6 +179,232 @@ def _get_showtime_footer() -> str:
176
179
  return "{_get_showtime_footer()}"
177
180
 
178
181
 
182
+ def _validate_non_active_state(pr: PullRequest) -> bool:
183
+ """Check if PR is in a state where new work can begin
184
+
185
+ Args:
186
+ pr: PullRequest object with current state
187
+
188
+ Returns:
189
+ True if safe to start new work, False if another job is already active
190
+ """
191
+ if pr.current_show:
192
+ active_states = ["building", "built", "deploying", "running", "updating"]
193
+ if pr.current_show.status in active_states:
194
+ return False # Already active
195
+ return True # Safe to proceed
196
+
197
+
198
+ def _atomic_claim_environment(
199
+ pr_number: int, target_sha: str, github: GitHubInterface, dry_run: bool = False
200
+ ) -> bool:
201
+ """Atomically claim environment for this job using compare-and-swap pattern
202
+
203
+ Args:
204
+ pr_number: PR number to claim
205
+ target_sha: Target commit SHA
206
+ github: GitHub interface for label operations
207
+ dry_run: If True, simulate operations
208
+
209
+ Returns:
210
+ True if successfully claimed, False if another job already active or no triggers
211
+ """
212
+ from datetime import datetime
213
+
214
+ try:
215
+ # 1. CHECK: Load current PR state
216
+ pr = PullRequest.from_id(pr_number, github)
217
+
218
+ # 2. VALIDATE: Ensure non-active state (compare part of compare-and-swap)
219
+ if not _validate_non_active_state(pr):
220
+ current_state = pr.current_show.status if pr.current_show else "unknown"
221
+ console.print(
222
+ f"🎪 Environment already active (state: {current_state}) - another job is running"
223
+ )
224
+ return False
225
+
226
+ # 3. FIND TRIGGERS: Must have triggers to claim
227
+ trigger_labels = [label for label in pr.labels if "showtime-trigger-" in label]
228
+ if not trigger_labels:
229
+ console.print("🎪 No trigger labels found - nothing to claim")
230
+ return False
231
+
232
+ # 4. VALIDATE TRIGGER-SPECIFIC STATE REQUIREMENTS
233
+ for trigger_label in trigger_labels:
234
+ if "showtime-trigger-start" in trigger_label:
235
+ # Start trigger: should NOT already be building/running
236
+ if pr.current_show and pr.current_show.status in [
237
+ "building",
238
+ "built",
239
+ "deploying",
240
+ "running",
241
+ ]:
242
+ console.print(
243
+ f"🎪 Start trigger invalid - environment already {pr.current_show.status}"
244
+ )
245
+ return False
246
+ elif "showtime-trigger-stop" in trigger_label:
247
+ # Stop trigger: should HAVE an active environment
248
+ if not pr.current_show or pr.current_show.status in ["failed"]:
249
+ console.print("🎪 Stop trigger invalid - no active environment to stop")
250
+ return False
251
+
252
+ console.print(f"🎪 Claiming environment for PR #{pr_number} SHA {target_sha[:7]}")
253
+ console.print(f"🎪 Found {len(trigger_labels)} valid trigger(s) to process")
254
+
255
+ if dry_run:
256
+ console.print(
257
+ "🎪 [bold yellow]DRY-RUN[/bold yellow] - Would atomically claim environment"
258
+ )
259
+ return True
260
+
261
+ # 4. ATOMIC SWAP: Remove triggers + Set building state (swap part)
262
+ console.print("🎪 Executing atomic claim (remove triggers + set building)...")
263
+
264
+ # Remove all trigger labels first
265
+ for trigger_label in trigger_labels:
266
+ console.print(f" 🗑️ Removing trigger: {trigger_label}")
267
+ github.remove_label(pr_number, trigger_label)
268
+
269
+ # Immediately set building state to claim the environment
270
+ building_show = Show(
271
+ pr_number=pr_number,
272
+ sha=short_sha(target_sha),
273
+ status="building",
274
+ created_at=datetime.utcnow().strftime("%Y-%m-%dT%H-%M"),
275
+ ttl="24h",
276
+ requested_by=_get_github_actor(),
277
+ )
278
+
279
+ # Clear any stale state and set building labels atomically
280
+ github.remove_circus_labels(pr_number)
281
+ for label in building_show.to_circus_labels():
282
+ github.add_label(pr_number, label)
283
+
284
+ console.print("🎪 ✅ Environment claimed successfully")
285
+ return True
286
+
287
+ except Exception as e:
288
+ console.print(f"🎪 ❌ Failed to claim environment: {e}")
289
+ return False
290
+
291
+
292
+ def _build_docker_image(pr_number: int, sha: str, dry_run: bool = False) -> bool:
293
+ """Build Docker image directly without supersetbot dependency
294
+
295
+ Args:
296
+ pr_number: PR number for tagging
297
+ sha: Full commit SHA
298
+ dry_run: If True, print command but don't execute
299
+
300
+ Returns:
301
+ True if build succeeded, False if failed
302
+ """
303
+ tag = f"apache/superset:pr-{pr_number}-{short_sha(sha)}-ci"
304
+
305
+ cmd = [
306
+ "docker",
307
+ "buildx",
308
+ "build",
309
+ "--push",
310
+ "--load",
311
+ "--platform",
312
+ "linux/amd64",
313
+ "--target",
314
+ "ci",
315
+ "--build-arg",
316
+ "PY_VER=3.10-slim-bookworm",
317
+ "--build-arg",
318
+ "INCLUDE_CHROMIUM=false",
319
+ "--build-arg",
320
+ "LOAD_EXAMPLES_DUCKDB=true",
321
+ "-t",
322
+ tag,
323
+ ".",
324
+ ]
325
+
326
+ console.print(f"🐳 Building Docker image: {tag}")
327
+ if dry_run:
328
+ console.print(f"🎪 [bold yellow]DRY-RUN[/bold yellow] - Would run: {' '.join(cmd)}")
329
+ return True
330
+
331
+ try:
332
+ console.print(f"🎪 Running: {' '.join(cmd)}")
333
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=1800) # 30 min timeout
334
+
335
+ if result.returncode == 0:
336
+ console.print(f"🎪 ✅ Docker build succeeded: {tag}")
337
+ return True
338
+ else:
339
+ console.print("🎪 ❌ Docker build failed:")
340
+ console.print(f"Exit code: {result.returncode}")
341
+ console.print(f"STDOUT: {result.stdout}")
342
+ console.print(f"STDERR: {result.stderr}")
343
+ return False
344
+
345
+ except subprocess.TimeoutExpired:
346
+ console.print("🎪 ❌ Docker build timed out after 30 minutes")
347
+ return False
348
+ except Exception as e:
349
+ console.print(f"🎪 ❌ Docker build error: {e}")
350
+ return False
351
+
352
+
353
+ def _set_state_internal(
354
+ state: str,
355
+ pr_number: int,
356
+ show: Show,
357
+ github: GitHubInterface,
358
+ dry_run_github: bool = False,
359
+ error_msg: Optional[str] = None,
360
+ ) -> None:
361
+ """Internal helper to set state and handle comments/labels
362
+
363
+ Used by sync and other commands to set final state transitions
364
+ """
365
+ console.print(f"🎪 Setting state to '{state}' for PR #{pr_number} SHA {show.sha}")
366
+
367
+ # Update show state
368
+ show.status = state
369
+
370
+ # Handle state-specific logic
371
+ comment_text = None
372
+
373
+ if state == "building":
374
+ comment_text = building_comment(show)
375
+ console.print("🎪 Posting building comment...")
376
+
377
+ elif state == "running":
378
+ comment_text = success_comment(show)
379
+ console.print("🎪 Posting success comment...")
380
+
381
+ elif state == "failed":
382
+ error_message = error_msg or "Build or deployment failed"
383
+ comment_text = failure_comment(show, error_message)
384
+ console.print("🎪 Posting failure comment...")
385
+
386
+ elif state in ["built", "deploying"]:
387
+ console.print(f"🎪 Silent state change to '{state}' - no comment posted")
388
+
389
+ # Post comment if needed
390
+ if comment_text and not dry_run_github:
391
+ github.post_comment(pr_number, comment_text)
392
+ console.print("🎪 ✅ Comment posted!")
393
+ elif comment_text:
394
+ console.print("🎪 [bold yellow]DRY-RUN-GITHUB[/bold yellow] - Would post comment")
395
+
396
+ # Set state labels
397
+ state_labels = show.to_circus_labels()
398
+
399
+ if not dry_run_github:
400
+ github.remove_circus_labels(pr_number)
401
+ for label in state_labels:
402
+ github.add_label(pr_number, label)
403
+ console.print("🎪 ✅ Labels updated!")
404
+ else:
405
+ console.print("🎪 [bold yellow]DRY-RUN-GITHUB[/bold yellow] - Would update labels")
406
+
407
+
179
408
  @app.command()
180
409
  def version():
181
410
  """Show version information"""
@@ -184,6 +413,74 @@ def version():
184
413
  console.print(f"🎪 Superset Showtime v{__version__}")
185
414
 
186
415
 
416
+ @app.command()
417
+ def set_state(
418
+ state: str = typer.Argument(
419
+ ..., help="State to set (building, built, deploying, running, failed)"
420
+ ),
421
+ pr_number: int = typer.Argument(..., help="PR number to update"),
422
+ sha: Optional[str] = typer.Option(None, "--sha", help="Specific commit SHA (default: latest)"),
423
+ error_msg: Optional[str] = typer.Option(
424
+ None, "--error-msg", help="Error message for failed state"
425
+ ),
426
+ dry_run_github: bool = typer.Option(
427
+ False, "--dry-run-github", help="Skip GitHub operations, show what would happen"
428
+ ),
429
+ ):
430
+ """🎪 Set environment state (generic state transition command)
431
+
432
+ States:
433
+ • building - Docker image is being built (posts comment)
434
+ • built - Docker build complete, ready for deployment (silent)
435
+ • deploying - AWS deployment in progress (silent)
436
+ • running - Environment is live and ready (posts success comment)
437
+ • failed - Build or deployment failed (posts error comment)
438
+ """
439
+ from datetime import datetime
440
+
441
+ try:
442
+ github = GitHubInterface()
443
+
444
+ # Get SHA - use provided SHA or default to latest
445
+ if sha:
446
+ target_sha = sha
447
+ console.print(f"🎪 Using specified SHA: {target_sha[:7]}")
448
+ else:
449
+ target_sha = github.get_latest_commit_sha(pr_number)
450
+ console.print(f"🎪 Using latest SHA: {target_sha[:7]}")
451
+
452
+ # Validate state
453
+ valid_states = ["building", "built", "deploying", "running", "failed"]
454
+ if state not in valid_states:
455
+ console.print(f"❌ Invalid state: {state}. Must be one of: {', '.join(valid_states)}")
456
+ raise typer.Exit(1)
457
+
458
+ # Get GitHub actor
459
+ github_actor = _get_github_actor()
460
+
461
+ # Create or update show object
462
+ show = Show(
463
+ pr_number=pr_number,
464
+ sha=short_sha(target_sha),
465
+ status=state,
466
+ created_at=datetime.utcnow().strftime("%Y-%m-%dT%H-%M"),
467
+ ttl="24h",
468
+ requested_by=github_actor,
469
+ )
470
+
471
+ # Use internal helper to set state
472
+ _set_state_internal(state, pr_number, show, github, dry_run_github, error_msg)
473
+
474
+ console.print(f"🎪 [bold green]State successfully set to '{state}'[/bold green]")
475
+
476
+ except GitHubError as e:
477
+ console.print(f"❌ GitHub error: {e}")
478
+ raise typer.Exit(1) from e
479
+ except Exception as e:
480
+ console.print(f"❌ Error: {e}")
481
+ raise typer.Exit(1) from e
482
+
483
+
187
484
  @app.command()
188
485
  def start(
189
486
  pr_number: int = typer.Argument(..., help="PR number to create environment for"),
@@ -648,15 +945,15 @@ def sync(
648
945
  check_only: bool = typer.Option(
649
946
  False, "--check-only", help="Check what actions are needed without executing"
650
947
  ),
651
- deploy: bool = typer.Option(
652
- False, "--deploy", help="Execute deployment actions (assumes build is complete)"
653
- ),
654
948
  dry_run_aws: bool = typer.Option(
655
949
  False, "--dry-run-aws", help="Skip AWS operations, use mock data"
656
950
  ),
657
951
  dry_run_github: bool = typer.Option(
658
952
  False, "--dry-run-github", help="Skip GitHub label operations"
659
953
  ),
954
+ dry_run_docker: bool = typer.Option(
955
+ False, "--dry-run-docker", help="Skip Docker build, use mock success"
956
+ ),
660
957
  aws_sleep: int = typer.Option(
661
958
  0, "--aws-sleep", help="Seconds to sleep during AWS operations (for testing)"
662
959
  ),
@@ -685,18 +982,99 @@ def sync(
685
982
  action_needed = _determine_sync_action(pr, pr_state, target_sha)
686
983
 
687
984
  if check_only:
688
- # Output structured results for GitHub Actions
689
- console.print(f"action_needed={action_needed}")
690
-
691
- # Build needed for new environments and updates (SHA changes)
985
+ # Output simple results for GitHub Actions
692
986
  build_needed = action_needed in ["create_environment", "rolling_update", "auto_sync"]
693
- console.print(f"build_needed={str(build_needed).lower()}")
987
+ sync_needed = action_needed != "no_action"
694
988
 
695
- # Deploy needed for everything except no_action
696
- deploy_needed = action_needed != "no_action"
697
- console.print(f"deploy_needed={str(deploy_needed).lower()}")
989
+ console.print(f"build_needed={str(build_needed).lower()}")
990
+ console.print(f"sync_needed={str(sync_needed).lower()}")
991
+ console.print(f"pr_number={pr_number}")
992
+ console.print(f"target_sha={target_sha}")
698
993
  return
699
994
 
995
+ # Default behavior: execute the sync (directive command)
996
+ # Use --check-only to override this and do read-only analysis
997
+ if not check_only:
998
+ console.print(
999
+ f"🎪 [bold blue]Syncing PR #{pr_number}[/bold blue] (SHA: {target_sha[:7]})"
1000
+ )
1001
+ console.print(f"🎪 Action needed: {action_needed}")
1002
+
1003
+ # For trigger-based actions, use atomic claim to prevent race conditions
1004
+ if action_needed in ["create_environment", "rolling_update", "destroy_environment"]:
1005
+ if not _atomic_claim_environment(pr_number, target_sha, github, dry_run_github):
1006
+ console.print("🎪 Unable to claim environment - exiting")
1007
+ return
1008
+ console.print("🎪 ✅ Environment claimed - proceeding with work")
1009
+
1010
+ # Execute based on determined action
1011
+ if action_needed == "cleanup":
1012
+ console.print("🎪 PR is closed - cleaning up environment")
1013
+ if pr.current_show:
1014
+ _handle_stop_trigger(pr_number, github, dry_run_aws, dry_run_github)
1015
+ else:
1016
+ console.print("🎪 No environment to clean up")
1017
+ return
1018
+
1019
+ elif action_needed in ["create_environment", "rolling_update"]:
1020
+ # These require Docker build + deployment
1021
+ console.print(f"🎪 Starting {action_needed} workflow")
1022
+
1023
+ # Post building comment (atomic claim already set building state)
1024
+ if action_needed == "create_environment":
1025
+ console.print("🎪 Posting building comment...")
1026
+ elif action_needed == "rolling_update":
1027
+ console.print("🎪 Posting rolling update comment...")
1028
+
1029
+ # Build Docker image
1030
+ build_success = _build_docker_image(pr_number, target_sha, dry_run_docker)
1031
+ if not build_success:
1032
+ _set_state_internal(
1033
+ "failed",
1034
+ pr_number,
1035
+ Show(
1036
+ pr_number=pr_number,
1037
+ sha=short_sha(target_sha),
1038
+ status="failed",
1039
+ requested_by=_get_github_actor(),
1040
+ ),
1041
+ github,
1042
+ dry_run_github,
1043
+ "Docker build failed",
1044
+ )
1045
+ return
1046
+
1047
+ # Continue with AWS deployment (reuse existing logic)
1048
+ _handle_start_trigger(
1049
+ pr_number,
1050
+ github,
1051
+ dry_run_aws,
1052
+ dry_run_github,
1053
+ aws_sleep,
1054
+ docker_tag,
1055
+ force=True,
1056
+ )
1057
+ return
1058
+
1059
+ elif action_needed == "destroy_environment":
1060
+ console.print("🎪 Destroying environment")
1061
+ _handle_stop_trigger(pr_number, github, dry_run_aws, dry_run_github)
1062
+ return
1063
+
1064
+ elif action_needed == "auto_sync":
1065
+ console.print("🎪 Auto-sync on new commit")
1066
+ # This also requires build + deployment
1067
+ build_success = _build_docker_image(pr_number, target_sha, dry_run_docker)
1068
+ if build_success:
1069
+ _handle_sync_trigger(pr_number, github, dry_run_aws, dry_run_github, aws_sleep)
1070
+ else:
1071
+ console.print("🎪 ❌ Auto-sync failed due to build failure")
1072
+ return
1073
+
1074
+ else:
1075
+ console.print(f"🎪 No action needed ({action_needed})")
1076
+ return
1077
+
700
1078
  console.print(
701
1079
  f"🎪 [bold blue]Syncing PR #{pr_number}[/bold blue] (state: {pr_state}, SHA: {target_sha[:7]})"
702
1080
  )
@@ -1238,57 +1616,18 @@ def _handle_start_trigger(
1238
1616
  )
1239
1617
  _schedule_blue_cleanup(pr_number, blue_services)
1240
1618
 
1241
- # Update to running state with new SHA
1242
- show.status = "running"
1619
+ # Update show with deployment result
1243
1620
  show.ip = result.ip
1244
1621
 
1245
- # Traffic switching happens here - update GitHub labels atomically
1246
- running_labels = show.to_circus_labels()
1247
- console.print("\n🎪 Setting running state labels (traffic switch):")
1248
- for label in running_labels:
1249
- console.print(f" + {label}")
1250
-
1251
- if not dry_run_github:
1252
- console.print("🎪 Executing traffic switch via GitHub labels...")
1253
- # Remove existing circus labels first
1254
- github.remove_circus_labels(pr_number)
1255
- # Add new running labels - this switches traffic atomically
1256
- for label in running_labels:
1257
- github.add_label(pr_number, label)
1258
- console.print("🎪 ✅ Labels updated to running state!")
1259
-
1260
- # Post success comment with real IP
1261
- # Update show with real IP for comment
1262
- show.ip = result.ip
1263
- show.status = "running"
1264
- success_comment_text = success_comment(show, feature_count=len(feature_flags))
1265
-
1266
- github.post_comment(pr_number, success_comment_text)
1622
+ # Use internal helper to set running state (posts success comment)
1623
+ console.print("\n🎪 Traffic switching to running state:")
1624
+ _set_state_internal("running", pr_number, show, github, dry_run_github)
1267
1625
 
1268
1626
  else:
1269
1627
  console.print(f"🎪 [bold red]❌ AWS deployment failed:[/bold red] {result.error}")
1270
1628
 
1271
- # Update to failed state
1272
- show.status = "failed"
1273
- failed_labels = show.to_circus_labels()
1274
-
1275
- if not dry_run_github:
1276
- console.print("🎪 Setting failed state labels...")
1277
- github.remove_circus_labels(pr_number)
1278
- for label in failed_labels:
1279
- github.add_label(pr_number, label)
1280
-
1281
- # Post failure comment
1282
- failure_comment = f"""🎪 @{github_actor} Environment creation failed.
1283
-
1284
- **Error:** {result.error}
1285
- **Environment:** `{show.sha}`
1286
-
1287
- Please check the logs and try again.
1288
-
1289
- {_get_showtime_footer()}"""
1290
-
1291
- github.post_comment(pr_number, failure_comment)
1629
+ # Use internal helper to set failed state (posts failure comment)
1630
+ _set_state_internal("failed", pr_number, show, github, dry_run_github, result.error)
1292
1631
 
1293
1632
  except Exception as e:
1294
1633
  console.print(f"🎪 [bold red]Start trigger failed:[/bold red] {e}")
@@ -18,7 +18,7 @@ class Show:
18
18
 
19
19
  pr_number: int
20
20
  sha: str # 7-char commit SHA
21
- status: str # building, running, updating, failed
21
+ status: str # building, built, deploying, running, updating, failed
22
22
  ip: Optional[str] = None # Environment IP address
23
23
  created_at: Optional[str] = None # ISO timestamp
24
24
  ttl: str = "24h" # 24h, 48h, close, etc.
@@ -54,6 +54,16 @@ class Show:
54
54
  """Check if environment is currently building"""
55
55
  return self.status == "building"
56
56
 
57
+ @property
58
+ def is_built(self) -> bool:
59
+ """Check if environment is built (Docker complete, ready for deploy)"""
60
+ return self.status == "built"
61
+
62
+ @property
63
+ def is_deploying(self) -> bool:
64
+ """Check if environment is currently deploying to AWS"""
65
+ return self.status == "deploying"
66
+
57
67
  @property
58
68
  def is_updating(self) -> bool:
59
69
  """Check if environment is currently updating"""
@@ -96,8 +96,18 @@ def get_aws_console_urls(service_name: str) -> Dict[str, str]:
96
96
  # Typed comment functions with clear parameter requirements
97
97
 
98
98
 
99
+ def building_comment(show: Show) -> str:
100
+ """Create building start comment
101
+
102
+ Args:
103
+ show: Show object with SHA and other metadata
104
+ """
105
+ links = _create_header_links(show.sha)
106
+ return f"🎪 {links['showtime_link']} is building environment on {links['gha_link']} for {links['commit_link']}"
107
+
108
+
99
109
  def start_comment(show: Show) -> str:
100
- """Create environment start comment
110
+ """Create environment start comment (DEPRECATED - use building_comment)
101
111
 
102
112
  Args:
103
113
  show: Show object with SHA and other metadata
@@ -3,7 +3,7 @@ name: 🎪 Superset Showtime
3
3
  # Ultra-simple: just sync on any PR state change
4
4
  on:
5
5
  pull_request_target:
6
- types: [labeled, unlabeled, synchronize, closed]
6
+ types: [labeled, synchronize, closed]
7
7
 
8
8
  # Manual testing
9
9
  workflow_dispatch:
@@ -57,20 +57,27 @@ jobs:
57
57
  fi
58
58
 
59
59
  echo "Using PR number: $PR_NUM"
60
- OUTPUT=$(python -m showtime sync $PR_NUM --check-only)
60
+
61
+ # Run sync check-only with optional SHA override
62
+ if [[ -n "${{ github.event.inputs.sha }}" ]]; then
63
+ OUTPUT=$(python -m showtime sync $PR_NUM --check-only --sha "${{ github.event.inputs.sha }}")
64
+ else
65
+ OUTPUT=$(python -m showtime sync $PR_NUM --check-only)
66
+ fi
61
67
  echo "$OUTPUT"
62
68
 
63
- # Extract outputs for conditional steps
64
- ACTION=$(echo "$OUTPUT" | grep "action_needed=" | cut -d'=' -f2)
69
+ # Extract the outputs we need for conditional steps
65
70
  BUILD=$(echo "$OUTPUT" | grep "build_needed=" | cut -d'=' -f2)
66
- DEPLOY=$(echo "$OUTPUT" | grep "deploy_needed=" | cut -d'=' -f2)
71
+ SYNC=$(echo "$OUTPUT" | grep "sync_needed=" | cut -d'=' -f2)
72
+ PR_NUM_OUT=$(echo "$OUTPUT" | grep "pr_number=" | cut -d'=' -f2)
73
+ TARGET_SHA=$(echo "$OUTPUT" | grep "target_sha=" | cut -d'=' -f2)
67
74
 
68
- echo "action_needed=$ACTION" >> $GITHUB_OUTPUT
69
75
  echo "build_needed=$BUILD" >> $GITHUB_OUTPUT
70
- echo "deploy_needed=$DEPLOY" >> $GITHUB_OUTPUT
71
- echo "pr_number=$PR_NUM" >> $GITHUB_OUTPUT
76
+ echo "sync_needed=$SYNC" >> $GITHUB_OUTPUT
77
+ echo "pr_number=$PR_NUM_OUT" >> $GITHUB_OUTPUT
78
+ echo "target_sha=$TARGET_SHA" >> $GITHUB_OUTPUT
72
79
 
73
- - name: Setup Docker Environment
80
+ - name: Setup Docker Environment (only if build needed)
74
81
  if: steps.check.outputs.build_needed == 'true'
75
82
  uses: ./.github/actions/setup-docker
76
83
  with:
@@ -78,58 +85,20 @@ jobs:
78
85
  dockerhub-token: ${{ env.DOCKERHUB_TOKEN }}
79
86
  build: "true"
80
87
 
81
- - name: Setup supersetbot
82
- if: steps.check.outputs.build_needed == 'true'
83
- uses: ./.github/actions/setup-supersetbot/
84
-
85
- - name: Get PR SHA for build
86
- id: sha
87
- if: steps.check.outputs.build_needed == 'true'
88
- run: |
89
- # Simple SHA handling - use input or default to PR head
90
- if [[ -n "${{ github.event.inputs.sha }}" ]]; then
91
- SHA="${{ github.event.inputs.sha }}"
92
- echo "Using override SHA: $SHA"
93
- elif [[ -n "${{ github.event.pull_request.head.sha }}" ]]; then
94
- SHA="${{ github.event.pull_request.head.sha }}"
95
- echo "Using PR head SHA: $SHA"
96
- else
97
- echo "❌ No SHA available from PR head or inputs"
98
- exit 1
99
- fi
100
-
101
- # Validate SHA format (40-char hex)
102
- if [[ ! "$SHA" =~ ^[a-f0-9]{40}$ ]]; then
103
- echo "❌ Invalid SHA format: $SHA"
104
- exit 1
105
- fi
106
-
107
- echo "build_sha=$SHA" >> $GITHUB_OUTPUT
108
-
109
- - name: Checkout PR code
88
+ - name: Checkout PR code (only if build needed)
110
89
  if: steps.check.outputs.build_needed == 'true'
111
90
  uses: actions/checkout@v4
112
91
  with:
113
- ref: ${{ steps.sha.outputs.build_sha }}
92
+ ref: ${{ steps.check.outputs.target_sha }}
114
93
  persist-credentials: false
115
94
 
116
- - name: Build Docker image
117
- if: steps.check.outputs.build_needed == 'true'
118
- run: |
119
- supersetbot docker \
120
- --push \
121
- --load \
122
- --preset ci \
123
- --platform linux/amd64 \
124
- --context-ref "${{ steps.sha.outputs.build_sha }}" \
125
- --extra-flags "--build-arg INCLUDE_CHROMIUM=false --build-arg LOAD_EXAMPLES_DUCKDB=true"
126
-
127
- - name: Execute sync deployment
128
- if: steps.check.outputs.deploy_needed == 'true'
95
+ - name: Execute sync (handles everything)
96
+ if: steps.check.outputs.sync_needed == 'true'
129
97
  run: |
130
98
  PR_NUM="${{ steps.check.outputs.pr_number }}"
131
- if [[ -n "${{ github.event.inputs.sha }}" ]]; then
132
- python -m showtime sync $PR_NUM --deploy --sha "${{ github.event.inputs.sha }}"
99
+ TARGET_SHA="${{ steps.check.outputs.target_sha }}"
100
+ if [[ -n "$TARGET_SHA" ]]; then
101
+ python -m showtime sync $PR_NUM --sha "$TARGET_SHA"
133
102
  else
134
- python -m showtime sync $PR_NUM --deploy
103
+ python -m showtime sync $PR_NUM
135
104
  fi