Skip to content

Commit d4e887d

Browse files
committed
feat(deploy): generate QR code locally with qrencode
- Replace external QR service with local qrencode generation - QR code embedded as base64 data URI (no external API calls) - Add test workflow for QR generation, input validation, payload construction - Add justfile for development tasks (auto-manages podman) - Update CONTRIBUTING.md with simplified testing instructions
1 parent bb9db1c commit d4e887d

8 files changed

Lines changed: 248 additions & 9 deletions

File tree

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
name: Test Components
2+
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
7+
permissions:
8+
contents: read
9+
10+
jobs:
11+
test-qr-generation:
12+
name: Test QR Code Generation
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- name: Install qrencode
18+
run: sudo apt-get update && sudo apt-get install -y qrencode
19+
20+
- name: Test QR code generation
21+
run: |
22+
URL="https://editor-pr123-regel-k4c.rig.prd1.gn2.quattro.rijksapps.nl"
23+
24+
# Generate QR code as base64
25+
QR_BASE64=$(qrencode -o - -t PNG -s 4 -m 1 "$URL" | base64 -w0)
26+
27+
# Verify output is valid base64 PNG
28+
echo "Base64 length: ${#QR_BASE64}"
29+
if [ ${#QR_BASE64} -lt 100 ]; then
30+
echo "QR code generation failed - output too small"
31+
exit 1
32+
fi
33+
34+
# Verify it starts with PNG magic bytes (base64 encoded)
35+
if [[ ! "$QR_BASE64" =~ ^iVBOR ]]; then
36+
echo "QR code generation failed - not a valid PNG"
37+
exit 1
38+
fi
39+
40+
echo "QR code generated successfully"
41+
42+
# Create test HTML
43+
echo "<img src='data:image/png;base64,${QR_BASE64}' width='100' height='100'>" > qr-test.html
44+
45+
- name: Upload test artifact
46+
if: ${{ !env.ACT }}
47+
uses: actions/upload-artifact@v4
48+
with:
49+
name: qr-test
50+
path: qr-test.html
51+
52+
test-input-validation:
53+
name: Test Input Validation
54+
runs-on: ubuntu-latest
55+
steps:
56+
- uses: actions/checkout@v4
57+
58+
- name: Test valid inputs
59+
run: |
60+
# These should all pass validation
61+
for input in "my-project" "pr123" "my.service" "foo_bar" "CamelCase"; do
62+
if ! echo "$input" | grep -qE '^[a-zA-Z0-9._-]+$'; then
63+
echo "FAIL: '$input' should be valid"
64+
exit 1
65+
fi
66+
echo "PASS: '$input' is valid"
67+
done
68+
69+
- name: Test invalid inputs
70+
run: |
71+
# These should all fail validation
72+
for input in "has space" "has;semicolon" 'has"quote' "has\$dollar" "has/slash"; do
73+
if echo "$input" | grep -qE '^[a-zA-Z0-9._-]+$'; then
74+
echo "FAIL: '$input' should be invalid"
75+
exit 1
76+
fi
77+
echo "PASS: '$input' is correctly rejected"
78+
done
79+
80+
test-payload-construction:
81+
name: Test Payload Construction
82+
runs-on: ubuntu-latest
83+
steps:
84+
- uses: actions/checkout@v4
85+
86+
- name: Test basic payload
87+
run: |
88+
DEPLOYMENT_NAME="pr123"
89+
COMPONENT="editor"
90+
IMAGE="ghcr.io/org/app:latest"
91+
92+
PAYLOAD=$(jq -n \
93+
--arg name "$DEPLOYMENT_NAME" \
94+
--arg ref "$COMPONENT" \
95+
--arg image "$IMAGE" \
96+
'{
97+
deploymentName: $name,
98+
components: [{
99+
reference: $ref,
100+
image: $image
101+
}]
102+
}')
103+
104+
echo "$PAYLOAD" | jq .
105+
106+
# Verify structure
107+
if [ "$(echo "$PAYLOAD" | jq -r '.deploymentName')" != "pr123" ]; then
108+
echo "FAIL: deploymentName mismatch"
109+
exit 1
110+
fi
111+
echo "PASS: Basic payload constructed correctly"
112+
113+
- name: Test clone payload
114+
run: |
115+
DEPLOYMENT_NAME="pr123"
116+
COMPONENT="editor"
117+
IMAGE="ghcr.io/org/app:latest"
118+
CLONE_FROM="production"
119+
120+
PAYLOAD=$(jq -n \
121+
--arg name "$DEPLOYMENT_NAME" \
122+
--arg ref "$COMPONENT" \
123+
--arg image "$IMAGE" \
124+
--arg clone "$CLONE_FROM" \
125+
--argjson force true \
126+
'{
127+
deploymentName: $name,
128+
cloneFrom: $clone,
129+
forceClone: $force,
130+
components: [{
131+
reference: $ref,
132+
image: $image
133+
}]
134+
}')
135+
136+
echo "$PAYLOAD" | jq .
137+
138+
# Verify clone fields
139+
if [ "$(echo "$PAYLOAD" | jq -r '.cloneFrom')" != "production" ]; then
140+
echo "FAIL: cloneFrom mismatch"
141+
exit 1
142+
fi
143+
if [ "$(echo "$PAYLOAD" | jq -r '.forceClone')" != "true" ]; then
144+
echo "FAIL: forceClone mismatch"
145+
exit 1
146+
fi
147+
echo "PASS: Clone payload constructed correctly"

.pre-commit-ci.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
ci:
2+
autoupdate_schedule: weekly
3+
# Skip hooks that are already run by CI workflow (avoid duplicates)
4+
skip: [shellcheck, actionlint, yamllint]

.pre-commit-config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,9 @@ repos:
1717
- id: end-of-file-fixer
1818
- id: check-yaml
1919
- id: check-added-large-files
20+
21+
- repo: https://github.com/adrienverge/yamllint
22+
rev: v1.35.1
23+
hooks:
24+
- id: yamllint
25+
args: [-d, "{extends: relaxed, rules: {line-length: {max: 150}}}"]

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- Automatically post/update a comment on PRs with the deployment URL
1313
- New inputs: `comment-on-pr`, `github-token`, `comment-header`
1414
- Upsert behavior: updates existing comment instead of creating duplicates
15+
- **deploy** action: QR code in PR comment
16+
- New input: `qr-code` (default: `true`)
17+
- QR code for easy mobile testing of preview deployments
18+
- Generated locally using `qrencode` (no external API calls, privacy-friendly)
1519
- **cleanup** action: PR comment update feature
1620
- Update the deploy PR comment to show cleanup status when PR is closed
1721
- New inputs: `update-pr-comment`, `comment-header`
@@ -23,6 +27,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2327
- SECURITY.md with security policy
2428
- Pre-commit hooks configuration
2529

30+
### Internal
31+
- Added test workflow for local testing with act + Podman
32+
- Added justfile for common development tasks
33+
2634
### Fixed
2735
- ShellCheck warnings: properly quoted GITHUB_OUTPUT
2836
- Actionlint configuration to only lint workflow files

CONTRIBUTING.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@ Thank you for your interest in contributing to ZAD Actions!
77
### Prerequisites
88

99
- Git
10+
- [just](https://github.com/casey/just) (command runner)
1011
- [pre-commit](https://pre-commit.com/) (for local linting)
1112
- [ShellCheck](https://www.shellcheck.net/) (for bash script linting)
1213
- [actionlint](https://github.com/rhysd/actionlint) (for GitHub Actions validation)
14+
- [act](https://github.com/nektos/act) (for local GitHub Actions testing)
15+
- [Podman](https://podman.io/) (container runtime for act)
1316

1417
### Setting Up Pre-commit Hooks
1518

@@ -37,10 +40,17 @@ pre-commit run --all-files
3740

3841
### Testing Locally
3942

40-
Run the pre-commit hooks to validate your changes:
43+
Use the justfile for common tasks:
4144

4245
```bash
43-
pre-commit run --all-files
46+
# List available commands
47+
just
48+
49+
# Run linting
50+
just lint
51+
52+
# Run tests with act + podman (auto-starts/stops podman if needed)
53+
just test
4454
```
4555

4656
### Testing in a Workflow

deploy/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Deploys a container image to ZAD Operations Manager.
1717
| `comment-on-pr` | No | `false` | Post/update a comment on the PR with the deployment URL |
1818
| `github-token` | No | `''` | GitHub token for PR commenting (needs `pull-requests: write`) |
1919
| `comment-header` | No | `## 🚀 Preview Deployment` | Custom header for the PR comment |
20+
| `qr-code` | No | `true` | Include QR code for mobile access (generated locally via qrencode) |
2021

2122
## Outputs
2223

@@ -86,12 +87,16 @@ The action will create a comment like this on the PR:
8687
8788
> ## 🚀 Preview Deployment
8889
>
90+
> <img src="data:image/png;base64,..." width="100" height="100" align="right" alt="QR code">
91+
>
8992
> Your changes have been deployed to a preview environment:
9093
>
9194
> **URL:** https://web-pr123-my-project.rig.prd1.gn2.quattro.rijksapps.nl
9295
>
9396
> This deployment will be automatically cleaned up when the PR is closed.
9497
98+
The QR code is generated locally using `qrencode` (no external API calls), making it easy to open the preview on your phone for mobile testing. To disable it, set `qr-code: false`.
99+
95100
On subsequent deployments to the same PR, the existing comment is updated instead of creating a new one.
96101

97102
### Use with GitHub Environment

deploy/action.yml

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ inputs:
4747
description: 'Custom header for the PR comment (default: "## 🚀 Preview Deployment")'
4848
required: false
4949
default: '## 🚀 Preview Deployment'
50+
qr-code:
51+
description: 'Include QR code in PR comment for mobile access (generated locally via qrencode)'
52+
required: false
53+
default: 'true'
5054

5155
outputs:
5256
url:
@@ -190,15 +194,27 @@ runs:
190194
COMMENT_HEADER: ${{ inputs.comment-header }}
191195
PR_NUMBER: ${{ github.event.pull_request.number }}
192196
GITHUB_REPOSITORY: ${{ github.repository }}
197+
QR_CODE: ${{ inputs.qr-code }}
193198
run: |
194-
# Build the comment body
195-
COMMENT_BODY="${COMMENT_HEADER}
196-
197-
Your changes have been deployed to a preview environment:
198-
199-
**URL:** ${DEPLOYMENT_URL}
199+
# Build QR code HTML if enabled
200+
QR_HTML=""
201+
if [ "$QR_CODE" = "true" ]; then
202+
# Generate QR code locally using qrencode (no external API)
203+
sudo apt-get install -y qrencode >/dev/null 2>&1 || true
204+
if command -v qrencode &> /dev/null; then
205+
QR_BASE64=$(qrencode -o - -t PNG -s 4 -m 1 "$DEPLOYMENT_URL" | base64 -w0)
206+
QR_HTML="<img src='data:image/png;base64,${QR_BASE64}' width='100' height='100' align='right' alt='QR code to preview'>"
207+
else
208+
echo "Warning: qrencode not available, skipping QR code"
209+
fi
210+
fi
200211
201-
This deployment will be automatically cleaned up when the PR is closed."
212+
# Build the comment body
213+
COMMENT_BODY="${COMMENT_HEADER}"$'\n\n'
214+
COMMENT_BODY+="${QR_HTML}"$'\n\n'
215+
COMMENT_BODY+="Your changes have been deployed to a preview environment:"$'\n\n'
216+
COMMENT_BODY+="**URL:** ${DEPLOYMENT_URL}"$'\n\n'
217+
COMMENT_BODY+="This deployment will be automatically cleaned up when the PR is closed."
202218
203219
# Check for existing comment from this action (identified by header)
204220
EXISTING_COMMENT_ID=$(gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \

justfile

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
[private]
2+
default:
3+
@just --list
4+
5+
# Run all tests locally with act + podman
6+
test:
7+
#!/usr/bin/env bash
8+
set -euo pipefail
9+
10+
# Check if podman is running
11+
STARTED_PODMAN=false
12+
if ! podman machine inspect --format '{{{{.State}}' 2>/dev/null | grep -q "running"; then
13+
echo "Starting podman machine..."
14+
podman machine start
15+
STARTED_PODMAN=true
16+
fi
17+
18+
# Cleanup function
19+
cleanup() {
20+
if [ "$STARTED_PODMAN" = true ]; then
21+
echo "Stopping podman machine..."
22+
podman machine stop
23+
fi
24+
}
25+
trap cleanup EXIT
26+
27+
# Run tests
28+
DOCKER_HOST="unix://$(podman machine inspect --format '{{{{.ConnectionInfo.PodmanSocket.Path}}')" \
29+
act pull_request -W .github/workflows/test-components.yml --bind=false --container-daemon-socket "-"
30+
31+
# Run pre-commit hooks on all files
32+
lint:
33+
pre-commit run --all-files
34+
35+
# Start podman machine
36+
[private]
37+
podman-start:
38+
podman machine start
39+
40+
# Stop podman machine
41+
[private]
42+
podman-stop:
43+
podman machine stop

0 commit comments

Comments
 (0)