Skip to content

Commit fd8ddfb

Browse files
committed
Refactor GitHub Actions workflows for multi-platform builds and remove reusable workflow
- Transition to inline matrix builds for multi-platform support (amd64/arm64) in ESP-IDF and PlatformIO workflows. - Remove the reusable docker-build.yml workflow, consolidating build logic directly into each image workflow. - Implement environment variables for registry and image name to streamline configuration. - Enhance version handling by defining versions directly in the matrix strategy, allowing for easy expansion to multiple versions. - Update job structures to include manifest creation for multi-arch images and ensure only the master branch pushes images to GHCR. - Improve readability with emoji in step names and clarify testing best practices for branch strategy.
1 parent 748137a commit fd8ddfb

File tree

4 files changed

+280
-332
lines changed

4 files changed

+280
-332
lines changed

.cursor/rules/github-actions-standards.mdc

Lines changed: 125 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -34,92 +34,128 @@ Add emoji to step names for readability:
3434
Each image should have its own workflow file:
3535
- Naming: `.github/workflows/<image-name>.yml`
3636
- Trigger on relevant file changes only
37-
- Use reusable workflow: `.github/workflows/docker-build.yml`
37+
- Use inline matrix builds for multi-platform support (amd64/arm64)
3838
- Images are **only pushed to GHCR from master branch**
3939
- Dev and PR branches build only (no push)
4040

4141
### Workflow Structure
4242

43-
Standard pattern for image workflows:
43+
Standard pattern for image workflows with matrix builds:
4444

4545
```yaml
4646
name: 🐳 <Image Name> Docker Image
4747

48-
env:
49-
DEFAULT_VERSION: v1.0.0
50-
5148
on:
5249
push:
5350
branches: [master, dev]
5451
paths:
5552
- '.github/workflows/<image-name>.yml'
56-
- '.github/workflows/docker-build.yml'
5753
- 'images/<image-name>/**'
5854
pull_request:
5955
branches: [master, dev]
6056
paths:
6157
- '.github/workflows/<image-name>.yml'
62-
- '.github/workflows/docker-build.yml'
6358
- 'images/<image-name>/**'
64-
workflow_dispatch:
65-
inputs:
66-
version:
67-
description: 'Version (e.g., v1.0.0)'
68-
required: true
69-
type: string
70-
force_rebuild:
71-
description: 'Force rebuild without cache'
72-
required: false
73-
default: false
74-
type: boolean
75-
custom_tags:
76-
description: 'Additional custom tags (comma-separated, optional)'
77-
required: false
78-
default: ''
79-
type: string
59+
60+
env:
61+
REGISTRY: ghcr.io
62+
IMAGE_NAME: jethome-dev-<image-name>
8063

8164
jobs:
82-
prepare:
65+
build:
8366
runs-on: ubuntu-latest
84-
outputs:
85-
version: ${{ steps.version.outputs.version }}
67+
permissions:
68+
contents: read
69+
packages: write
70+
strategy:
71+
matrix:
72+
platform: [linux/amd64, linux/arm64]
73+
version: ['1.0.0'] # Can expand to multiple versions
8674
steps:
87-
- name: 🏷️ Resolve version
88-
id: version
75+
- name: 📥 Checkout repository
76+
uses: actions/checkout@v4
77+
78+
- name: 🔧 Set up Docker Buildx
79+
uses: docker/setup-buildx-action@v3
80+
81+
- name: 🔐 Log in to GitHub Container Registry
82+
if: github.ref_name == 'master'
83+
uses: docker/login-action@v3
84+
with:
85+
registry: ${{ env.REGISTRY }}
86+
username: ${{ github.actor }}
87+
password: ${{ secrets.GITHUB_TOKEN }}
88+
89+
- name: 🏷️ Prepare platform tag
90+
id: platform
8991
run: |
90-
VERSION="${{ github.event.inputs.version || env.DEFAULT_VERSION }}"
91-
echo "version=${VERSION}" >> $GITHUB_OUTPUT
92-
echo "Using version: ${VERSION}"
93-
94-
build:
95-
needs: prepare
96-
uses: ./.github/workflows/docker-build.yml
92+
PLATFORM_PAIR=${{ matrix.platform }}
93+
PLATFORM_TAG=$(echo $PLATFORM_PAIR | sed 's/\//-/g')
94+
echo "tag=${PLATFORM_TAG}" >> $GITHUB_OUTPUT
95+
96+
- name: 🐳 Build and push Docker image
97+
uses: docker/build-push-action@v5
98+
with:
99+
context: images/<image-name>
100+
platforms: ${{ matrix.platform }}
101+
push: ${{ github.ref_name == 'master' }}
102+
tags: |
103+
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:<prefix>-${{ matrix.version }}-${{ steps.platform.outputs.tag }}
104+
cache-from: type=gha,scope=${{ env.IMAGE_NAME }}-${{ steps.platform.outputs.tag }}
105+
cache-to: type=gha,mode=max,scope=${{ env.IMAGE_NAME }}-${{ steps.platform.outputs.tag }}
106+
build-args: |
107+
VERSION=${{ matrix.version }}
108+
109+
create-manifest:
110+
runs-on: ubuntu-latest
111+
needs: build
112+
if: github.ref_name == 'master'
97113
permissions:
98114
contents: read
99115
packages: write
100-
secrets: inherit
101-
with:
102-
image_name: jethome-dev-<image-name>
103-
context_path: images/<image-name>
104-
version_tag: <prefix>-${{ needs.prepare.outputs.version }}
105-
force_rebuild: ${{ github.event.inputs.force_rebuild == 'true' }}
106-
custom_tags: ${{ github.event.inputs.custom_tags || '' }}
107-
build_args: |
108-
VERSION=${{ needs.prepare.outputs.version }}
116+
strategy:
117+
matrix:
118+
version: ['1.0.0'] # Must match build job matrix
119+
steps:
120+
- name: 🔐 Log in to GitHub Container Registry
121+
uses: docker/login-action@v3
122+
with:
123+
registry: ${{ env.REGISTRY }}
124+
username: ${{ github.actor }}
125+
password: ${{ secrets.GITHUB_TOKEN }}
126+
127+
- name: 🏷️ Generate version and date tags
128+
id: tags
129+
run: |
130+
SHA_SHORT=$(echo "${{ github.sha }}" | cut -c1-7)
131+
DATE_TAG=$(date +%Y.%m.%d)
132+
echo "sha_short=${SHA_SHORT}" >> $GITHUB_OUTPUT
133+
echo "date_tag=${DATE_TAG}" >> $GITHUB_OUTPUT
134+
135+
- name: 🎯 Create multi-arch manifest
136+
run: |
137+
docker buildx imagetools create -t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:latest \
138+
-t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:stable \
139+
-t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:<prefix>-${{ matrix.version }} \
140+
-t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:${{ steps.tags.outputs.date_tag }} \
141+
-t ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:sha-${{ steps.tags.outputs.sha_short }} \
142+
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:<prefix>-${{ matrix.version }}-linux-amd64 \
143+
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:<prefix>-${{ matrix.version }}-linux-arm64
109144
```
110145

111146
**Critical Details:**
112-
- `secrets: inherit` - Required for reusable workflows to access GITHUB_TOKEN
113-
- `force_rebuild: ${{ ... == 'true' }}` - Convert string to boolean (not `|| false`)
114-
- Boolean inputs from workflow_dispatch come as strings and need explicit comparison
147+
- Matrix builds both platforms in parallel for faster CI
148+
- Platform-specific images tagged with arch suffix (e.g., `pio-6.1.18-linux-amd64`)
149+
- Manifest job combines platform images into multi-arch manifest
150+
- Only master branch pushes images; dev/PR do build-only validation
151+
- Version matrix can be expanded to build multiple versions
115152

116153
### Version Handling
117154

118-
- Define default versions in `env` at workflow level (single source of truth)
119-
- Use `prepare` job to resolve version (inputs or env default)
120-
- Pass resolved version via job outputs to build job
121-
- `env` context available in job steps, outputs available in `with:`
122-
- Easy to override manually via GitHub UI (workflow_dispatch inputs)
155+
- Define versions directly in matrix strategy
156+
- Can expand to multiple versions: `version: ['1.0.0', '1.1.0']`
157+
- Each version builds for all platforms in the matrix
158+
- Matrix generates all combinations: platforms × versions
123159

124160
### Push Behavior by Branch
125161

@@ -149,23 +185,19 @@ Test workflows locally using `act`:
149185

150186
Use `gh` CLI to test workflows on GitHub Actions:
151187

152-
**Manual Workflow Dispatch:**
188+
**Triggering Workflows:**
189+
190+
Workflows are triggered automatically by pushing to master or dev branches. To test:
191+
153192
```bash
154-
# Trigger workflow with parameters
155-
gh workflow run "🐳 <Workflow Name>" --ref master \
156-
-f version=v1.0.0 \
157-
-f force_rebuild=true \
158-
-f custom_tags=test
159-
160-
# Example: ESP-IDF
161-
gh workflow run "🐳 ESP-IDF Docker Image" --ref master \
162-
-f esp_idf_version=v5.4.1 \
163-
-f force_rebuild=true
164-
165-
# Example: PlatformIO
166-
gh workflow run "🐳 PlatformIO Docker Image" --ref master \
167-
-f pio_version=6.1.18 \
168-
-f force_rebuild=false
193+
# Push to dev branch for testing (build-only, no push to GHCR)
194+
git checkout dev
195+
git push origin dev
196+
197+
# After validation, merge to master (builds and pushes to GHCR)
198+
git checkout master
199+
git merge dev
200+
git push origin master
169201
```
170202

171203
**Monitoring Workflows:**
@@ -199,62 +231,41 @@ gh api /repos/:owner/:repo/actions/runs/<run-id>/jobs | \
199231
jq -r '.jobs[] | "\(.name): \(.status) - \(.conclusion // "N/A")"'
200232
```
201233

202-
**Automated Testing Script Example:**
234+
**Monitoring Latest Run:**
203235
```bash
204-
#!/bin/bash
205-
# Trigger workflow and wait for completion
206-
236+
# Get latest workflow run for a specific workflow
207237
WORKFLOW="🐳 ESP-IDF Docker Image"
208-
VERSION="v5.4.1"
209-
210-
# Trigger workflow
211-
gh workflow run "$WORKFLOW" --ref master \
212-
-f esp_idf_version=$VERSION \
213-
-f force_rebuild=true
214-
215-
# Wait for workflow to appear
216-
sleep 10
217-
218-
# Get latest run ID
219238
RUN_ID=$(gh run list --workflow="$WORKFLOW" --limit 1 \
220239
--json databaseId -q '.[0].databaseId')
221240

222-
echo "Monitoring workflow run: $RUN_ID"
223-
224241
# Monitor until completion
225-
while true; do
226-
STATUS=$(gh run view $RUN_ID --json status,conclusion | \
227-
jq -r '.status')
228-
229-
if [ "$STATUS" = "completed" ]; then
230-
CONCLUSION=$(gh run view $RUN_ID --json conclusion | \
231-
jq -r '.conclusion')
232-
echo "Workflow completed: $CONCLUSION"
233-
234-
if [ "$CONCLUSION" = "success" ]; then
235-
exit 0
236-
else
237-
gh run view $RUN_ID --log-failed
238-
exit 1
239-
fi
240-
fi
241-
242-
echo "Status: $STATUS"
243-
sleep 20
244-
done
242+
gh run watch $RUN_ID
243+
244+
# Check final status
245+
CONCLUSION=$(gh run view $RUN_ID --json conclusion -q '.conclusion')
246+
if [ "$CONCLUSION" = "success" ]; then
247+
echo "✅ Workflow succeeded"
248+
else
249+
echo "❌ Workflow failed"
250+
gh run view $RUN_ID --log-failed
251+
fi
245252
```
246253

247254
### Testing Best Practices
248255

249256
1. **Local first**: Test with `act` before pushing
250-
2. **Force rebuild**: Use `force_rebuild=true` to bypass Docker cache when testing fixes
251-
- Required when Dockerfile changes but Docker might use cached layers
252-
- The `no-cache` option in reusable workflow ensures true rebuild
257+
2. **Branch strategy**: Always test on `dev` branch first
258+
- Push to dev for build validation (no GHCR push)
259+
- Review workflow logs for both platforms
260+
- Merge to master only after successful dev validation
253261
3. **Monitor logs**: Always check logs for build errors, even on "success"
254-
- Check both amd64 and arm64 build logs if multi-platform
262+
- Check both amd64 and arm64 build logs (matrix creates separate jobs)
255263
- Look for 0.1s completion times (indicates QEMU failure on arm64)
256-
4. **Verify image**: Pull and test the published image after successful build
257-
- Test with actual commands: `docker run --rm <image> pytest --version`
258-
- Verify imports work: `python3 -c 'import pytest_embedded'`
259-
5. **Branch strategy**: Test on `dev` branch first, then merge to `master` for release
260-
- Remember: dev builds don't push to GHCR (build validation only)
264+
- Verify both platform builds complete successfully
265+
4. **Verify images**: Pull and test the published image after successful master build
266+
- Test multi-arch manifest: `docker pull <image>:latest`
267+
- Verify on your architecture: `docker run --rm <image>:latest <command>`
268+
- Check that correct platform is pulled automatically
269+
5. **Cache management**: GitHub Actions cache persists between runs
270+
- Cache is per-platform (separate for amd64/arm64)
271+
- To force fresh build, clear GitHub Actions cache via Settings → Actions → Caches

0 commit comments

Comments
 (0)