Skip to content

Commit efa02d2

Browse files
committed
Fix TestMain execution and add Docker cache management
- Move TestMain from testcontainers_testmain.go to provider_test.go so it's included in test builds - Delete testcontainers_testmain.go (no longer needed) - Add 'make clean' command to aggressively clear Docker cache and test artifacts - Add platform workarounds for MySQL 5.6/5.7 and Percona on Apple Silicon - Add warnings for MySQL versions without ARM64 support - TestMain now properly executes and sets up shared containers
1 parent 0cfb952 commit efa02d2

File tree

4 files changed

+471
-101
lines changed

4 files changed

+471
-101
lines changed

GORELEASER_GPGSIGNING_PLAN.md

Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
# GoReleaser GPG Code Signing Plan for GitHub Actions
2+
3+
## Current State
4+
5+
1. **GoReleaser Configuration**: `.goreleaser.yml` is configured to sign checksums using GPG
6+
- Expects `GPG_FINGERPRINT` environment variable
7+
- Uses `--batch` flag for non-interactive signing
8+
- Signs the SHA256 checksum file
9+
10+
2. **GitHub Actions Status**: Release job is currently **DISABLED** (commented out)
11+
- Comment indicates: "DISABLED to figure out GPG signing issue on Github Actions"
12+
- Suspected issue: "possibly due to lack of TTY inside docker?"
13+
14+
3. **Existing Secrets**: There's a reference to `GPG_PRIVATE_KEY` secret in the Terraform Provider Release workflow
15+
16+
## Plan Overview
17+
18+
### Phase 1: GPG Subkey Creation and Export
19+
20+
**Objective**: Create a signing subkey from your main key and export it securely
21+
22+
**Why Subkeys?**
23+
- Your main key stays secure on your local machine
24+
- Subkeys can be exported for CI/CD use
25+
- If a subkey is compromised, it can be revoked without affecting your main key
26+
- Subkeys can have specific capabilities (signing only, encryption only, etc.)
27+
28+
1. **List Your Existing Keys**
29+
```bash
30+
# List your secret keys to find your main key
31+
gpg --list-secret-keys --keyid-format LONG
32+
# Note the KEY_ID (the long hex string after "sec")
33+
```
34+
35+
2. **Create a Signing Subkey**
36+
37+
**Option A: Non-Interactive Method (Recommended - GPG 2.1+)**
38+
```bash
39+
# Create a signing-only RSA 4096-bit subkey with no expiration
40+
# Replace YOUR_MAIN_KEY_ID with your actual main key ID
41+
gpg --quick-add-key YOUR_MAIN_KEY_ID rsa4096 sign 0
42+
43+
# Or if you want it to expire in 2 years:
44+
# gpg --quick-add-key YOUR_MAIN_KEY_ID rsa4096 sign 2y
45+
46+
# You'll be prompted for your main key passphrase
47+
```
48+
49+
**Option B: Interactive Method (if quick-add-key doesn't work)**
50+
```bash
51+
# This requires an interactive terminal (not batch mode)
52+
# Make sure you're in a terminal with TTY access
53+
gpg --expert --edit-key YOUR_MAIN_KEY_ID
54+
55+
# In the GPG prompt:
56+
# 1. Type: addkey
57+
# 2. Select: (4) RSA (sign only)
58+
# 3. Choose key size: 4096 (or your preference)
59+
# 4. Set expiration: 0 (no expiration) or a specific date
60+
# 5. Confirm: y
61+
# 6. Enter your main key passphrase
62+
# 7. Type: save
63+
```
64+
65+
**Note**: If you get "can't do this in batch mode" error, you need to:
66+
- Run it in an interactive terminal (not through a script)
67+
- Or use Option A with `--quick-add-key` which works in batch mode
68+
69+
3. **Export the Signing Subkey**
70+
```bash
71+
# List keys again to see the new subkey
72+
gpg --list-secret-keys --keyid-format LONG YOUR_MAIN_KEY_ID
73+
# Look for the "ssb" line - that's your signing subkey
74+
# Note the SUBKEY_ID (the long hex string after "ssb")
75+
76+
# Export ONLY the subkeys (not the main key)
77+
# This exports all subkeys of the main key, which is fine
78+
gpg --armor --export-secret-subkeys YOUR_MAIN_KEY_ID > gpg-signing-subkey.asc
79+
80+
# Get the subkey fingerprint (this is what GoReleaser will use)
81+
# Use --with-subkey-fingerprints to show subkey fingerprints
82+
gpg --fingerprint --with-subkey-fingerprints YOUR_MAIN_KEY_ID
83+
84+
# Or use --list-secret-keys with LONG format to see subkey IDs
85+
gpg --list-secret-keys --keyid-format LONG YOUR_MAIN_KEY_ID
86+
87+
# The signing subkey will show as "ssb" with [S] flag
88+
# Example output:
89+
# ssb rsa4096/ABC123DEF4567890 2024-01-01 [S] [expires: never]
90+
# └─ This is the subkey ID (short form, 16 chars)
91+
#
92+
# To get the full 40-character fingerprint of a specific subkey:
93+
gpg --fingerprint --with-subkey-fingerprints YOUR_MAIN_KEY_ID | grep -A 1 "\[S\]"
94+
# This will show the signing subkey with its full fingerprint
95+
```
96+
97+
4. **Verify Subkey Export**
98+
```bash
99+
# Verify only the subkey was exported (not the main key)
100+
gpg --list-packets gpg-signing-subkey.asc | grep -E "(keyid|secret)"
101+
# Should only show the subkey, not the main key
102+
```
103+
104+
3. **Store in GitHub Secrets**
105+
- Add `GPG_PRIVATE_KEY`: The ASCII-armored subkey content from `gpg-signing-subkey.asc`
106+
- Add `GPG_FINGERPRINT`: The subkey fingerprint (40-character hex string)
107+
- Add `GPG_PASSPHRASE`: The passphrase for your main key (required for password-protected keys)
108+
109+
**Important**: Store the subkey, not your main key! This way your main key stays secure.
110+
111+
### Phase 2: GitHub Actions Workflow Updates
112+
113+
**Objective**: Configure the release job to import and use GPG key
114+
115+
1. **Update `.github/workflows/main.yml`** - Uncomment and enhance the release job:
116+
117+
```yaml
118+
release:
119+
name: Release
120+
needs: [tests]
121+
if: ( startsWith( github.ref, 'refs/tags/v' ) ||
122+
startsWith(github.ref, 'refs/tags/v0.0.0-rc') )
123+
runs-on: ubuntu-22.04
124+
permissions:
125+
contents: write # Required for creating releases
126+
id-token: write # Required for OIDC if using
127+
steps:
128+
- name: Checkout Git repo
129+
uses: actions/checkout@v4
130+
with:
131+
fetch-depth: 0 # Full history needed for changelog
132+
133+
- name: Set up Go
134+
uses: actions/setup-go@v4
135+
with:
136+
go-version-file: go.mod
137+
138+
- name: Import GPG Subkey
139+
env:
140+
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
141+
GPG_FINGERPRINT: ${{ secrets.GPG_FINGERPRINT }}
142+
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
143+
run: |
144+
# Create GPG directory
145+
mkdir -p ~/.gnupg
146+
chmod 700 ~/.gnupg
147+
148+
# Configure GPG for non-interactive use with passphrase
149+
echo "use-agent" >> ~/.gnupg/gpg.conf
150+
echo "pinentry-mode loopback" >> ~/.gnupg/gpg.conf
151+
echo "allow-loopback-pinentry" >> ~/.gnupg/gpg.conf
152+
153+
# Start gpg-agent with loopback pinentry
154+
gpg-agent --daemon --allow-loopback-pinentry
155+
156+
# Import the subkey
157+
echo "$GPG_PRIVATE_KEY" | gpg --batch --import --passphrase "$GPG_PASSPHRASE"
158+
159+
# Trust the key (required for signing)
160+
# Use ultimate trust (6) for the subkey
161+
echo "$GPG_FINGERPRINT:6:" | gpg --import-ownertrust
162+
163+
# Verify key is available and can sign
164+
gpg --list-secret-keys --keyid-format LONG
165+
166+
# Test signing capability
167+
echo "test" | gpg --batch --pinentry-mode loopback --passphrase "$GPG_PASSPHRASE" --sign --armor
168+
169+
- name: Run GoReleaser
170+
uses: goreleaser/goreleaser-action@v6
171+
with:
172+
distribution: goreleaser
173+
version: '~> v2'
174+
args: release --clean --skip=validate
175+
env:
176+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
177+
GPG_FINGERPRINT: ${{ secrets.GPG_FINGERPRINT }}
178+
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
179+
GPG_TTY: $(tty) # Ensure GPG can access TTY if needed
180+
```
181+
182+
### Phase 3: GoReleaser Configuration Updates
183+
184+
**Objective**: Update `.goreleaser.yml` to sign all artifacts (not just checksums)
185+
186+
1. **Update Signing Configuration** in `.goreleaser.yml`:
187+
- Sign checksums (already configured)
188+
- Sign all binaries/archives (add this)
189+
- Add `--pinentry-mode loopback` for passphrase handling
190+
- Add `--passphrase` flag to use GPG_PASSPHRASE environment variable
191+
192+
2. **Updated `.goreleaser.yml` signing section**:
193+
```yaml
194+
signs:
195+
- artifacts: checksum
196+
args:
197+
- "--batch"
198+
- "--pinentry-mode"
199+
- "loopback"
200+
- "--passphrase"
201+
- "{{ .Env.GPG_PASSPHRASE }}"
202+
- "--local-user"
203+
- "{{ .Env.GPG_FINGERPRINT }}"
204+
- "--output"
205+
- "${signature}"
206+
- "--detach-sign"
207+
- "${artifact}"
208+
- artifacts: archive
209+
args:
210+
- "--batch"
211+
- "--pinentry-mode"
212+
- "loopback"
213+
- "--passphrase"
214+
- "{{ .Env.GPG_PASSPHRASE }}"
215+
- "--local-user"
216+
- "{{ .Env.GPG_FINGERPRINT }}"
217+
- "--output"
218+
- "${signature}"
219+
- "--detach-sign"
220+
- "${artifact}"
221+
```
222+
223+
3. **Alternative: Sign binaries directly** (if you want to sign the binaries themselves):
224+
```yaml
225+
signs:
226+
- artifacts: binary
227+
args:
228+
- "--batch"
229+
- "--pinentry-mode"
230+
- "loopback"
231+
- "--passphrase"
232+
- "{{ .Env.GPG_PASSPHRASE }}"
233+
- "--local-user"
234+
- "{{ .Env.GPG_FINGERPRINT }}"
235+
- "--output"
236+
- "${signature}"
237+
- "--detach-sign"
238+
- "${artifact}"
239+
- artifacts: checksum
240+
# ... same as above
241+
```
242+
243+
### Phase 4: Testing and Validation
244+
245+
**Objective**: Ensure the signing works correctly
246+
247+
1. **Test Workflow**:
248+
- Create a test tag (e.g., `v0.0.0-test`)
249+
- Push tag to trigger workflow
250+
- Verify release is created
251+
- Download and verify GPG signature:
252+
```bash
253+
gpg --verify terraform-provider-mysql_VERSION_SHA256SUMS.sig terraform-provider-mysql_VERSION_SHA256SUMS
254+
```
255+
256+
2. **Verify Signatures**:
257+
- Check that `.sig` files are created
258+
- Verify signatures are valid
259+
- Ensure public key can be found (consider uploading to keyserver or GitHub)
260+
261+
### Phase 5: Documentation Updates
262+
263+
**Objective**: Document the setup for future reference
264+
265+
1. **Update README.md**:
266+
- Add section on GitHub Actions GPG signing setup
267+
- Document required secrets
268+
- Add verification instructions
269+
270+
2. **Create Setup Guide** (optional):
271+
- Step-by-step guide for setting up GPG keys
272+
- Instructions for adding GitHub secrets
273+
- Troubleshooting section
274+
275+
## Security Considerations
276+
277+
1. **Key Management**:
278+
- ✅ **Use a subkey** (not your main key) - This is the recommended approach!
279+
- Your main key stays secure on your local machine
280+
- Only the signing subkey is exported and stored in GitHub Secrets
281+
- If the subkey is compromised, you can revoke it without affecting your main key
282+
- Rotate subkeys periodically (create new ones, revoke old ones)
283+
- Store subkey securely in GitHub Secrets (encrypted at rest)
284+
285+
2. **Subkey Benefits**:
286+
- Main key never leaves your machine
287+
- Subkeys can be scoped to specific capabilities (signing only)
288+
- Revocation is easier and doesn't affect your main identity
289+
- Can create multiple subkeys for different purposes
290+
291+
2. **Access Control**:
292+
- Limit who can modify GitHub Secrets
293+
- Use branch protection rules for release tags
294+
- Consider using GitHub Environments for additional protection
295+
296+
3. **Key Exposure**:
297+
- Never commit private keys to repository
298+
- Use GitHub Secrets (not environment variables in workflow files)
299+
- Consider using GitHub's OIDC for enhanced security
300+
301+
## Alternative Approaches
302+
303+
If GPG signing continues to be problematic:
304+
305+
1. **Cosign**: Use Sigstore/cosign for code signing (modern alternative)
306+
2. **GitHub's Built-in Signing**: Use GitHub's automatic code signing (if available)
307+
3. **Skip Signing**: Accept unsigned releases (not recommended for production)
308+
309+
## Implementation Checklist
310+
311+
- [ ] List existing GPG keys to find main key ID
312+
- [ ] Create signing subkey from main key
313+
- [ ] Export signing subkey (not main key!)
314+
- [ ] Add `GPG_PRIVATE_KEY` secret to GitHub (subkey only)
315+
- [ ] Add `GPG_FINGERPRINT` secret to GitHub (subkey fingerprint)
316+
- [ ] Add `GPG_PASSPHRASE` secret to GitHub (main key passphrase)
317+
- [ ] Update `.goreleaser.yml` to sign all artifacts
318+
- [ ] Update GitHub Actions workflow with GPG import steps
319+
- [ ] Test with a release tag
320+
- [ ] Verify signatures work (checksums and archives)
321+
- [ ] Update documentation
322+
- [ ] Enable release job
323+
- [ ] Test full release process
324+
325+
## References
326+
327+
- [GoReleaser Signing Documentation](https://goreleaser.com/customization/sign/)
328+
- [GitHub Actions GPG Signing Guide](https://docs.github.com/en/actions/security-guides/encrypted-secrets)
329+
- [GPG Batch Mode Documentation](https://www.gnupg.org/documentation/manuals/gnupg/GPG-Configuration.html)

Makefile

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,28 @@ default: help
5252
build: fmtcheck ## Build the provider
5353
go install
5454

55+
clean: ## Aggressively clear Docker cache and test artifacts
56+
@echo "Clearing Docker cache and test artifacts..."
57+
@# Remove testcontainers-related images (mysql, percona, mariadb, tidb)
58+
@docker images --format "{{.Repository}}:{{.Tag}}" | grep -E "(mysql|percona|mariadb|tidb|pingcap)" | xargs -r docker rmi -f 2>/dev/null || true
59+
@# Remove Docker manifests for problematic images (force remove even if they don't exist)
60+
@for img in mysql:5.6 mysql:5.7 percona:5.7 percona:8.0; do \
61+
docker manifest rm $$img 2>/dev/null || true; \
62+
done
63+
@# Prune build cache (all, not just 24h)
64+
@docker builder prune -af 2>/dev/null || true
65+
@# Prune unused images (all, not just 24h)
66+
@docker image prune -af 2>/dev/null || true
67+
@# Prune unused containers
68+
@docker container prune -f 2>/dev/null || true
69+
@# Prune unused networks (but keep default networks)
70+
@docker network prune -f 2>/dev/null || true
71+
@# Clear testcontainers temp files
72+
@rm -rf /tmp/testcontainers-* 2>/dev/null || true
73+
@# Clear Docker's content-addressable storage for problematic images (if possible)
74+
@echo "Docker cache cleared. Note: For MySQL 5.6/5.7 and Percona on Apple Silicon,"
75+
@echo "you may need to restart Docker Desktop to fully clear manifest cache."
76+
5577
test: testcontainers-matrix ## Run all acceptance tests
5678
test-sequential: acceptance
5779

@@ -84,7 +106,18 @@ test-mysql-%: ## Run tests against MySQL version (e.g., test-mysql-8.0)
84106
@$(MAKE) testversion$*
85107

86108
testversion%: ## Run tests against MySQL version (e.g., testversion8.0) [backwards compatible]
87-
@DOCKER_IMAGE=mysql:$* PATH="$(CURDIR)/bin:${PATH}" TF_ACC=1 GOTOOLCHAIN=auto go test -tags=testcontainers ./mysql/... -v $(if $(TESTARGS),-run "$(TESTARGS).*WithTestcontainers",-run WithTestcontainers) -timeout=30m
109+
@# MySQL 5.6 and 5.7 don't have ARM64 builds - Docker Desktop on Apple Silicon has manifest cache issues
110+
@# The workaround: restart Docker Desktop or use CI (GitHub Actions uses linux/amd64)
111+
@if [ "$*" = "5.6" ] || [ "$*" = "5.7" ]; then \
112+
echo "WARNING: MySQL $* doesn't have ARM64 support. Docker Desktop manifest cache may cause issues."; \
113+
echo "If tests fail with 'no match for platform in manifest', try: make clean && restart Docker Desktop"; \
114+
docker rmi mysql:$* 2>/dev/null || true; \
115+
docker manifest rm mysql:$* 2>/dev/null || true; \
116+
docker pull --platform linux/amd64 mysql:$* 2>&1 | grep -v "no match" || true; \
117+
DOCKER_DEFAULT_PLATFORM=linux/amd64 DOCKER_IMAGE=mysql:$* PATH="$(CURDIR)/bin:${PATH}" TF_ACC=1 GOTOOLCHAIN=auto go test -tags=testcontainers ./mysql/... -v $(if $(TESTARGS),-run "$(TESTARGS).*WithTestcontainers",-run WithTestcontainers) -timeout=30m; \
118+
else \
119+
DOCKER_IMAGE=mysql:$* PATH="$(CURDIR)/bin:${PATH}" TF_ACC=1 GOTOOLCHAIN=auto go test -tags=testcontainers ./mysql/... -v $(if $(TESTARGS),-run "$(TESTARGS).*WithTestcontainers",-run WithTestcontainers) -timeout=30m; \
120+
fi
88121

89122
testversion: ## Run tests against MySQL version (set MYSQL_VERSION)
90123
@DOCKER_IMAGE=mysql:$(MYSQL_VERSION) PATH="$(CURDIR)/bin:${PATH}" TF_ACC=1 GOTOOLCHAIN=auto go test -tags=testcontainers ./mysql/... -v $(if $(TESTARGS),-run "$(TESTARGS).*WithTestcontainers",-run WithTestcontainers) -timeout=30m
@@ -95,6 +128,13 @@ test-percona-%: ## Run tests against Percona version (e.g., test-percona-8.0)
95128
@$(MAKE) testpercona$*
96129

97130
testpercona%: ## Run tests against Percona version (e.g., testpercona8.0) [backwards compatible]
131+
@# Percona 5.7 and 8.0 don't have ARM64 builds, so pre-pull with platform specification for Apple Silicon
132+
@if [ "$*" = "5.7" ] || [ "$*" = "8.0" ]; then \
133+
echo "Pre-pulling percona:$* with platform linux/amd64 for Apple Silicon compatibility..."; \
134+
docker rmi percona:$* 2>/dev/null || true; \
135+
docker manifest rm percona:$* 2>/dev/null || true; \
136+
docker pull --platform linux/amd64 percona:$* || true; \
137+
fi
98138
@DOCKER_IMAGE=percona:$* PATH="$(CURDIR)/bin:${PATH}" TF_ACC=1 GOTOOLCHAIN=auto go test -tags=testcontainers ./mysql/... -v $(if $(TESTARGS),-run "$(TESTARGS).*WithTestcontainers",-run WithTestcontainers) -timeout=30m
99139

100140
testpercona: ## Run tests against Percona version (set MYSQL_VERSION)

0 commit comments

Comments
 (0)