Skip to content

Commit 6938d46

Browse files
committed
fix(ci): update workflows to support manual triggers and conditional execution based on Docker build success
1 parent ab0bc15 commit 6938d46

6 files changed

Lines changed: 579 additions & 126 deletions

File tree

.github/workflows/cerberus-integration.yml

Lines changed: 127 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,11 @@
11
name: Cerberus Integration Tests
22

33
on:
4-
push:
5-
branches: [ main, development, 'feature/**' ]
6-
paths:
7-
- 'backend/internal/caddy/**'
8-
- 'backend/internal/security/**'
9-
- 'backend/internal/handlers/security*.go'
10-
- 'backend/internal/models/security*.go'
11-
- 'scripts/cerberus_integration.sh'
12-
- 'Dockerfile'
13-
- '.github/workflows/cerberus-integration.yml'
14-
pull_request:
15-
branches: [ main, development ]
16-
paths:
17-
- 'backend/internal/caddy/**'
18-
- 'backend/internal/security/**'
19-
- 'backend/internal/handlers/security*.go'
20-
- 'backend/internal/models/security*.go'
21-
- 'scripts/cerberus_integration.sh'
22-
- 'Dockerfile'
23-
- '.github/workflows/cerberus-integration.yml'
24-
# Allow manual trigger
4+
workflow_run:
5+
workflows: ["Docker Build, Publish & Test"]
6+
types: [completed]
7+
branches: [main, development, 'feature/**'] # Explicit branch filter prevents unexpected triggers
8+
# Allow manual trigger for debugging
259
workflow_dispatch:
2610

2711
concurrency:
@@ -33,19 +17,134 @@ jobs:
3317
name: Cerberus Security Stack Integration
3418
runs-on: ubuntu-latest
3519
timeout-minutes: 20
20+
# Only run if docker-build.yml succeeded, or if manually triggered
21+
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
3622

3723
steps:
3824
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
3925

40-
- name: Set up Docker Buildx
41-
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
26+
# Determine the correct image tag based on trigger context
27+
# For PRs: pr-{number}-{sha}, For branches: {sanitized-branch}-{sha}
28+
- name: Determine image tag
29+
id: image
30+
env:
31+
EVENT: ${{ github.event.workflow_run.event }}
32+
REF: ${{ github.event.workflow_run.head_branch }}
33+
SHA: ${{ github.event.workflow_run.head_sha }}
34+
MANUAL_TAG: ${{ inputs.image_tag }}
35+
run: |
36+
# Manual trigger uses provided tag
37+
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
38+
if [[ -n "$MANUAL_TAG" ]]; then
39+
echo "tag=${MANUAL_TAG}" >> $GITHUB_OUTPUT
40+
else
41+
# Default to latest if no tag provided
42+
echo "tag=latest" >> $GITHUB_OUTPUT
43+
fi
44+
echo "source_type=manual" >> $GITHUB_OUTPUT
45+
exit 0
46+
fi
47+
48+
# Extract 7-character short SHA
49+
SHORT_SHA=$(echo "$SHA" | cut -c1-7)
50+
51+
if [[ "$EVENT" == "pull_request" ]]; then
52+
# Use native pull_requests array (no API calls needed)
53+
PR_NUM=$(echo '${{ toJson(github.event.workflow_run.pull_requests) }}' | jq -r '.[0].number')
54+
55+
if [[ -z "$PR_NUM" || "$PR_NUM" == "null" ]]; then
56+
echo "❌ ERROR: Could not determine PR number"
57+
echo "Event: $EVENT"
58+
echo "Ref: $REF"
59+
echo "SHA: $SHA"
60+
echo "Pull Requests JSON: ${{ toJson(github.event.workflow_run.pull_requests) }}"
61+
exit 1
62+
fi
63+
64+
# Immutable tag with SHA suffix prevents race conditions
65+
echo "tag=pr-${PR_NUM}-${SHORT_SHA}" >> $GITHUB_OUTPUT
66+
echo "source_type=pr" >> $GITHUB_OUTPUT
67+
else
68+
# Branch push: sanitize branch name and append SHA
69+
# Sanitization: lowercase, replace / with -, remove special chars
70+
SANITIZED=$(echo "$REF" | \
71+
tr '[:upper:]' '[:lower:]' | \
72+
tr '/' '-' | \
73+
sed 's/[^a-z0-9-._]/-/g' | \
74+
sed 's/^-//; s/-$//' | \
75+
sed 's/--*/-/g' | \
76+
cut -c1-121) # Leave room for -SHORT_SHA (7 chars)
77+
78+
echo "tag=${SANITIZED}-${SHORT_SHA}" >> $GITHUB_OUTPUT
79+
echo "source_type=branch" >> $GITHUB_OUTPUT
80+
fi
81+
82+
echo "sha=${SHORT_SHA}" >> $GITHUB_OUTPUT
83+
echo "Determined image tag: $(cat $GITHUB_OUTPUT | grep tag=)"
84+
85+
# Pull image from registry with retry logic (dual-source strategy)
86+
# Try registry first (fast), fallback to artifact if registry fails
87+
- name: Pull Docker image from registry
88+
id: pull_image
89+
uses: nick-fields/retry@v3
90+
with:
91+
timeout_minutes: 5
92+
max_attempts: 3
93+
retry_wait_seconds: 10
94+
command: |
95+
IMAGE_NAME="ghcr.io/${{ github.repository_owner }}/charon:${{ steps.image.outputs.tag }}"
96+
echo "Pulling image: $IMAGE_NAME"
97+
docker pull "$IMAGE_NAME"
98+
docker tag "$IMAGE_NAME" charon:local
99+
echo "✅ Successfully pulled from registry"
100+
continue-on-error: true
42101

43-
- name: Build Docker image
102+
# Fallback: Download artifact if registry pull failed
103+
- name: Fallback to artifact download
104+
if: steps.pull_image.outcome == 'failure'
105+
env:
106+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
107+
SHA: ${{ steps.image.outputs.sha }}
44108
run: |
45-
docker build \
46-
--no-cache \
47-
--build-arg VCS_REF=${{ github.sha }} \
48-
-t charon:local .
109+
echo "⚠️ Registry pull failed, falling back to artifact..."
110+
111+
# Determine artifact name based on source type
112+
if [[ "${{ steps.image.outputs.source_type }}" == "pr" ]]; then
113+
PR_NUM=$(echo '${{ toJson(github.event.workflow_run.pull_requests) }}' | jq -r '.[0].number')
114+
ARTIFACT_NAME="pr-image-${PR_NUM}"
115+
else
116+
ARTIFACT_NAME="push-image"
117+
fi
118+
119+
echo "Downloading artifact: $ARTIFACT_NAME"
120+
gh run download ${{ github.event.workflow_run.id }} \
121+
--name "$ARTIFACT_NAME" \
122+
--dir /tmp/docker-image || {
123+
echo "❌ ERROR: Artifact download failed!"
124+
echo "Available artifacts:"
125+
gh run view ${{ github.event.workflow_run.id }} --json artifacts --jq '.artifacts[].name'
126+
exit 1
127+
}
128+
129+
docker load < /tmp/docker-image/charon-image.tar
130+
docker tag $(docker images --format "{{.Repository}}:{{.Tag}}" | head -1) charon:local
131+
echo "✅ Successfully loaded from artifact"
132+
133+
# Validate image freshness by checking SHA label
134+
- name: Validate image SHA
135+
env:
136+
SHA: ${{ steps.image.outputs.sha }}
137+
run: |
138+
LABEL_SHA=$(docker inspect charon:local --format '{{index .Config.Labels "org.opencontainers.image.revision"}}' | cut -c1-7)
139+
echo "Expected SHA: $SHA"
140+
echo "Image SHA: $LABEL_SHA"
141+
142+
if [[ "$LABEL_SHA" != "$SHA" ]]; then
143+
echo "⚠️ WARNING: Image SHA mismatch!"
144+
echo "Image may be stale. Proceeding with caution..."
145+
else
146+
echo "✅ Image SHA matches expected commit"
147+
fi
49148
50149
- name: Run Cerberus integration tests
51150
id: cerberus-test

.github/workflows/crowdsec-integration.yml

Lines changed: 127 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,11 @@
11
name: CrowdSec Integration Tests
22

33
on:
4-
push:
5-
branches: [ main, development, 'feature/**' ]
6-
paths:
7-
- 'backend/internal/crowdsec/**'
8-
- 'backend/internal/models/crowdsec*.go'
9-
- 'configs/crowdsec/**'
10-
- 'scripts/crowdsec_integration.sh'
11-
- 'scripts/crowdsec_decision_integration.sh'
12-
- 'scripts/crowdsec_startup_test.sh'
13-
- '.github/skills/integration-test-crowdsec*/**'
14-
- 'Dockerfile'
15-
- '.github/workflows/crowdsec-integration.yml'
16-
pull_request:
17-
branches: [ main, development ]
18-
paths:
19-
- 'backend/internal/crowdsec/**'
20-
- 'backend/internal/models/crowdsec*.go'
21-
- 'configs/crowdsec/**'
22-
- 'scripts/crowdsec_integration.sh'
23-
- 'scripts/crowdsec_decision_integration.sh'
24-
- 'scripts/crowdsec_startup_test.sh'
25-
- '.github/skills/integration-test-crowdsec*/**'
26-
- 'Dockerfile'
27-
- '.github/workflows/crowdsec-integration.yml'
28-
# Allow manual trigger
4+
workflow_run:
5+
workflows: ["Docker Build, Publish & Test"]
6+
types: [completed]
7+
branches: [main, development, 'feature/**'] # Explicit branch filter prevents unexpected triggers
8+
# Allow manual trigger for debugging
299
workflow_dispatch:
3010

3111
concurrency:
@@ -37,19 +17,134 @@ jobs:
3717
name: CrowdSec Bouncer Integration
3818
runs-on: ubuntu-latest
3919
timeout-minutes: 15
20+
# Only run if docker-build.yml succeeded, or if manually triggered
21+
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
4022

4123
steps:
4224
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
4325

44-
- name: Set up Docker Buildx
45-
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
26+
# Determine the correct image tag based on trigger context
27+
# For PRs: pr-{number}-{sha}, For branches: {sanitized-branch}-{sha}
28+
- name: Determine image tag
29+
id: image
30+
env:
31+
EVENT: ${{ github.event.workflow_run.event }}
32+
REF: ${{ github.event.workflow_run.head_branch }}
33+
SHA: ${{ github.event.workflow_run.head_sha }}
34+
MANUAL_TAG: ${{ inputs.image_tag }}
35+
run: |
36+
# Manual trigger uses provided tag
37+
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
38+
if [[ -n "$MANUAL_TAG" ]]; then
39+
echo "tag=${MANUAL_TAG}" >> $GITHUB_OUTPUT
40+
else
41+
# Default to latest if no tag provided
42+
echo "tag=latest" >> $GITHUB_OUTPUT
43+
fi
44+
echo "source_type=manual" >> $GITHUB_OUTPUT
45+
exit 0
46+
fi
47+
48+
# Extract 7-character short SHA
49+
SHORT_SHA=$(echo "$SHA" | cut -c1-7)
50+
51+
if [[ "$EVENT" == "pull_request" ]]; then
52+
# Use native pull_requests array (no API calls needed)
53+
PR_NUM=$(echo '${{ toJson(github.event.workflow_run.pull_requests) }}' | jq -r '.[0].number')
54+
55+
if [[ -z "$PR_NUM" || "$PR_NUM" == "null" ]]; then
56+
echo "❌ ERROR: Could not determine PR number"
57+
echo "Event: $EVENT"
58+
echo "Ref: $REF"
59+
echo "SHA: $SHA"
60+
echo "Pull Requests JSON: ${{ toJson(github.event.workflow_run.pull_requests) }}"
61+
exit 1
62+
fi
63+
64+
# Immutable tag with SHA suffix prevents race conditions
65+
echo "tag=pr-${PR_NUM}-${SHORT_SHA}" >> $GITHUB_OUTPUT
66+
echo "source_type=pr" >> $GITHUB_OUTPUT
67+
else
68+
# Branch push: sanitize branch name and append SHA
69+
# Sanitization: lowercase, replace / with -, remove special chars
70+
SANITIZED=$(echo "$REF" | \
71+
tr '[:upper:]' '[:lower:]' | \
72+
tr '/' '-' | \
73+
sed 's/[^a-z0-9-._]/-/g' | \
74+
sed 's/^-//; s/-$//' | \
75+
sed 's/--*/-/g' | \
76+
cut -c1-121) # Leave room for -SHORT_SHA (7 chars)
77+
78+
echo "tag=${SANITIZED}-${SHORT_SHA}" >> $GITHUB_OUTPUT
79+
echo "source_type=branch" >> $GITHUB_OUTPUT
80+
fi
81+
82+
echo "sha=${SHORT_SHA}" >> $GITHUB_OUTPUT
83+
echo "Determined image tag: $(cat $GITHUB_OUTPUT | grep tag=)"
84+
85+
# Pull image from registry with retry logic (dual-source strategy)
86+
# Try registry first (fast), fallback to artifact if registry fails
87+
- name: Pull Docker image from registry
88+
id: pull_image
89+
uses: nick-fields/retry@v3
90+
with:
91+
timeout_minutes: 5
92+
max_attempts: 3
93+
retry_wait_seconds: 10
94+
command: |
95+
IMAGE_NAME="ghcr.io/${{ github.repository_owner }}/charon:${{ steps.image.outputs.tag }}"
96+
echo "Pulling image: $IMAGE_NAME"
97+
docker pull "$IMAGE_NAME"
98+
docker tag "$IMAGE_NAME" charon:local
99+
echo "✅ Successfully pulled from registry"
100+
continue-on-error: true
46101

47-
- name: Build Docker image
102+
# Fallback: Download artifact if registry pull failed
103+
- name: Fallback to artifact download
104+
if: steps.pull_image.outcome == 'failure'
105+
env:
106+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
107+
SHA: ${{ steps.image.outputs.sha }}
48108
run: |
49-
docker build \
50-
--no-cache \
51-
--build-arg VCS_REF=${{ github.sha }} \
52-
-t charon:local .
109+
echo "⚠️ Registry pull failed, falling back to artifact..."
110+
111+
# Determine artifact name based on source type
112+
if [[ "${{ steps.image.outputs.source_type }}" == "pr" ]]; then
113+
PR_NUM=$(echo '${{ toJson(github.event.workflow_run.pull_requests) }}' | jq -r '.[0].number')
114+
ARTIFACT_NAME="pr-image-${PR_NUM}"
115+
else
116+
ARTIFACT_NAME="push-image"
117+
fi
118+
119+
echo "Downloading artifact: $ARTIFACT_NAME"
120+
gh run download ${{ github.event.workflow_run.id }} \
121+
--name "$ARTIFACT_NAME" \
122+
--dir /tmp/docker-image || {
123+
echo "❌ ERROR: Artifact download failed!"
124+
echo "Available artifacts:"
125+
gh run view ${{ github.event.workflow_run.id }} --json artifacts --jq '.artifacts[].name'
126+
exit 1
127+
}
128+
129+
docker load < /tmp/docker-image/charon-image.tar
130+
docker tag $(docker images --format "{{.Repository}}:{{.Tag}}" | head -1) charon:local
131+
echo "✅ Successfully loaded from artifact"
132+
133+
# Validate image freshness by checking SHA label
134+
- name: Validate image SHA
135+
env:
136+
SHA: ${{ steps.image.outputs.sha }}
137+
run: |
138+
LABEL_SHA=$(docker inspect charon:local --format '{{index .Config.Labels "org.opencontainers.image.revision"}}' | cut -c1-7)
139+
echo "Expected SHA: $SHA"
140+
echo "Image SHA: $LABEL_SHA"
141+
142+
if [[ "$LABEL_SHA" != "$SHA" ]]; then
143+
echo "⚠️ WARNING: Image SHA mismatch!"
144+
echo "Image may be stale. Proceeding with caution..."
145+
else
146+
echo "✅ Image SHA matches expected commit"
147+
fi
53148
54149
- name: Run CrowdSec integration tests
55150
id: crowdsec-test

0 commit comments

Comments
 (0)