Skip to content

Commit 6cbbbf2

Browse files
committed
Add reusable Kyverno CI workflow and fix CLI command
- Create kyverno-ci.yml reusable workflow for external repos - Fix kyverno-validate.yml to use 'kyverno apply' (not 'validate') - Update Kyverno CLI to v1.13.0 - Update ADR-0012 with reusable workflow documentation
1 parent 733a497 commit 6cbbbf2

File tree

3 files changed

+293
-21
lines changed

3 files changed

+293
-21
lines changed

.github/workflows/kyverno-ci.yml

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
# ============================================================================
2+
# CI/CD Hub - Kyverno Policy CI (Reusable Workflow)
3+
# ============================================================================
4+
# Validates Kyverno policies for syntax and optionally tests them against
5+
# fixture resources. Use this in your repo to ensure policy quality.
6+
#
7+
# Usage in caller workflow:
8+
# jobs:
9+
# kyverno:
10+
# uses: jguida941/ci-cd-hub/.github/workflows/kyverno-ci.yml@v1
11+
# with:
12+
# policies_dir: 'policies/kyverno'
13+
# run_tests: true
14+
#
15+
# See also: docs/adr/0012-kyverno-policies.md
16+
# ============================================================================
17+
18+
name: Kyverno CI
19+
20+
on:
21+
workflow_call:
22+
inputs:
23+
policies_dir:
24+
description: 'Directory containing Kyverno policy YAML files'
25+
type: string
26+
default: 'policies/kyverno'
27+
templates_dir:
28+
description: 'Directory containing Kyverno policy templates'
29+
type: string
30+
default: 'templates/kyverno'
31+
fixtures_dir:
32+
description: 'Directory containing test fixture resources'
33+
type: string
34+
default: 'fixtures/kyverno'
35+
run_tests:
36+
description: 'Run policy tests against fixtures'
37+
type: boolean
38+
default: false
39+
kyverno_version:
40+
description: 'Kyverno CLI version'
41+
type: string
42+
default: 'v1.13.0'
43+
fail_on_warn:
44+
description: 'Fail if policies produce warnings'
45+
type: boolean
46+
default: false
47+
workdir:
48+
description: 'Working directory (for monorepos)'
49+
type: string
50+
default: '.'
51+
52+
jobs:
53+
validate:
54+
name: Validate Kyverno Policies
55+
runs-on: ubuntu-latest
56+
defaults:
57+
run:
58+
working-directory: ${{ inputs.workdir }}
59+
60+
steps:
61+
- name: Checkout
62+
uses: actions/checkout@v4
63+
64+
- name: Install Kyverno CLI
65+
run: |
66+
set -euo pipefail
67+
VERSION="${{ inputs.kyverno_version }}"
68+
VERSION_NUM="${VERSION#v}"
69+
70+
echo "Installing Kyverno CLI ${VERSION}..."
71+
curl -sLO "https://github.com/kyverno/kyverno/releases/download/${VERSION}/kyverno-cli_${VERSION_NUM}_linux_x86_64.tar.gz"
72+
tar -xzf "kyverno-cli_${VERSION_NUM}_linux_x86_64.tar.gz"
73+
sudo mv kyverno /usr/local/bin/
74+
rm "kyverno-cli_${VERSION_NUM}_linux_x86_64.tar.gz"
75+
kyverno version
76+
77+
- name: Validate policy syntax
78+
id: validate
79+
run: |
80+
set -euo pipefail
81+
echo "Validating Kyverno policies in ${{ inputs.policies_dir }}..."
82+
83+
FAILED=0
84+
VALIDATED=0
85+
86+
# Check if policies directory exists
87+
if [ ! -d "${{ inputs.policies_dir }}" ]; then
88+
echo "::warning::Policies directory '${{ inputs.policies_dir }}' not found"
89+
echo "validated=0" >> $GITHUB_OUTPUT
90+
echo "failed=0" >> $GITHUB_OUTPUT
91+
exit 0
92+
fi
93+
94+
# Validate all policy files using apply --dry-run with a minimal resource
95+
for policy in ${{ inputs.policies_dir }}/*.yaml ${{ inputs.policies_dir }}/*.yml; do
96+
if [ -f "$policy" ]; then
97+
echo "Validating: $policy"
98+
# Use apply with /dev/null as resource to validate policy syntax
99+
if kyverno apply "$policy" --resource /dev/null 2>&1 | grep -v "no resource found"; then
100+
echo " OK"
101+
VALIDATED=$((VALIDATED + 1))
102+
else
103+
# Check if it's a real error or just "no resource" message
104+
OUTPUT=$(kyverno apply "$policy" --resource /dev/null 2>&1)
105+
if echo "$OUTPUT" | grep -qi "error\|invalid\|failed"; then
106+
echo " FAILED"
107+
echo "$OUTPUT"
108+
FAILED=$((FAILED + 1))
109+
else
110+
echo " OK (syntax valid)"
111+
VALIDATED=$((VALIDATED + 1))
112+
fi
113+
fi
114+
fi
115+
done
116+
117+
# Validate templates if directory exists
118+
if [ -d "${{ inputs.templates_dir }}" ]; then
119+
for template in ${{ inputs.templates_dir }}/*.yaml ${{ inputs.templates_dir }}/*.yml; do
120+
if [ -f "$template" ]; then
121+
echo "Validating template: $template"
122+
OUTPUT=$(kyverno apply "$template" --resource /dev/null 2>&1 || true)
123+
if echo "$OUTPUT" | grep -qi "error\|invalid\|failed"; then
124+
echo " FAILED"
125+
echo "$OUTPUT"
126+
FAILED=$((FAILED + 1))
127+
else
128+
echo " OK (syntax valid)"
129+
VALIDATED=$((VALIDATED + 1))
130+
fi
131+
fi
132+
done
133+
fi
134+
135+
echo "validated=$VALIDATED" >> $GITHUB_OUTPUT
136+
echo "failed=$FAILED" >> $GITHUB_OUTPUT
137+
138+
if [ "$FAILED" -gt 0 ]; then
139+
echo "::error::$FAILED policy validation(s) failed"
140+
exit 1
141+
fi
142+
143+
echo "All $VALIDATED policies validated successfully"
144+
145+
- name: Test policies against fixtures
146+
if: inputs.run_tests && inputs.fixtures_dir != ''
147+
run: |
148+
set -euo pipefail
149+
echo "Testing policies against fixtures in ${{ inputs.fixtures_dir }}..."
150+
151+
if [ ! -d "${{ inputs.fixtures_dir }}" ]; then
152+
echo "::warning::Fixtures directory '${{ inputs.fixtures_dir }}' not found, skipping tests"
153+
exit 0
154+
fi
155+
156+
# Apply each policy to each fixture
157+
for policy in ${{ inputs.policies_dir }}/*.yaml ${{ inputs.policies_dir }}/*.yml; do
158+
if [ -f "$policy" ]; then
159+
POLICY_NAME=$(basename "$policy" .yaml)
160+
echo ""
161+
echo "=== Testing policy: $POLICY_NAME ==="
162+
163+
for fixture in ${{ inputs.fixtures_dir }}/*.yaml ${{ inputs.fixtures_dir }}/*.yml; do
164+
if [ -f "$fixture" ]; then
165+
FIXTURE_NAME=$(basename "$fixture")
166+
echo -n " vs $FIXTURE_NAME: "
167+
168+
RESULT=$(kyverno apply "$policy" --resource "$fixture" 2>&1 || true)
169+
170+
if echo "$RESULT" | grep -q "pass: 1"; then
171+
echo "PASS"
172+
elif echo "$RESULT" | grep -q "fail: 1"; then
173+
echo "FAIL (policy enforced)"
174+
elif echo "$RESULT" | grep -q "warn: 1"; then
175+
echo "WARN"
176+
if [ "${{ inputs.fail_on_warn }}" = "true" ]; then
177+
echo "::warning::Policy $POLICY_NAME warned on $FIXTURE_NAME"
178+
fi
179+
else
180+
echo "SKIP (not applicable)"
181+
fi
182+
fi
183+
done
184+
fi
185+
done
186+
187+
- name: Generate Summary
188+
if: always()
189+
run: |
190+
cat >> $GITHUB_STEP_SUMMARY << EOF
191+
# Kyverno Policy Validation
192+
193+
| Metric | Value |
194+
|--------|-------|
195+
| Policies Validated | ${{ steps.validate.outputs.validated || '0' }} |
196+
| Validation Failures | ${{ steps.validate.outputs.failed || '0' }} |
197+
| Tests Run | ${{ inputs.run_tests }} |
198+
199+
## Policies Directory
200+
\`${{ inputs.policies_dir }}\`
201+
202+
EOF
203+
204+
if [ -d "${{ inputs.policies_dir }}" ]; then
205+
echo "| Policy | Status |" >> $GITHUB_STEP_SUMMARY
206+
echo "|--------|--------|" >> $GITHUB_STEP_SUMMARY
207+
for policy in ${{ inputs.policies_dir }}/*.yaml ${{ inputs.policies_dir }}/*.yml; do
208+
if [ -f "$policy" ]; then
209+
NAME=$(basename "$policy")
210+
echo "| \`$NAME\` | Validated |" >> $GITHUB_STEP_SUMMARY
211+
fi
212+
done
213+
fi
214+
215+
cat >> $GITHUB_STEP_SUMMARY << 'EOF'
216+
217+
---
218+
219+
See [Kyverno Documentation](https://kyverno.io/docs/) for policy reference.
220+
EOF

.github/workflows/kyverno-validate.yml

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# ============================================================================
2-
# Kyverno Policy Validation
2+
# Kyverno Policy Validation (Internal)
33
# ============================================================================
4-
# Validates Kyverno policy syntax without requiring a Kubernetes cluster.
4+
# Validates Kyverno policy syntax for the hub's own policies.
55
# Runs on changes to policy files to catch syntax errors early.
66
#
7+
# For external repos, use the reusable kyverno-ci.yml workflow instead.
8+
#
79
# See also: docs/adr/0012-kyverno-policies.md
810
# ============================================================================
911

@@ -13,11 +15,15 @@ on:
1315
push:
1416
paths:
1517
- 'policies/kyverno/**/*.yaml'
18+
- 'policies/kyverno/**/*.yml'
1619
- 'templates/kyverno/**/*.yaml'
20+
- 'templates/kyverno/**/*.yml'
1721
pull_request:
1822
paths:
1923
- 'policies/kyverno/**/*.yaml'
24+
- 'policies/kyverno/**/*.yml'
2025
- 'templates/kyverno/**/*.yaml'
26+
- 'templates/kyverno/**/*.yml'
2127
workflow_dispatch:
2228

2329
jobs:
@@ -30,69 +36,93 @@ jobs:
3036
uses: actions/checkout@v4
3137

3238
- name: Install Kyverno CLI
39+
env:
40+
KYVERNO_VERSION: "v1.13.0"
3341
run: |
34-
# Install Kyverno CLI for policy validation
35-
curl -LO https://github.com/kyverno/kyverno/releases/download/v1.11.4/kyverno-cli_v1.11.4_linux_x86_64.tar.gz
36-
tar -xzf kyverno-cli_v1.11.4_linux_x86_64.tar.gz
42+
set -euo pipefail
43+
VERSION_NUM="${KYVERNO_VERSION#v}"
44+
echo "Installing Kyverno CLI ${KYVERNO_VERSION}..."
45+
curl -sLO "https://github.com/kyverno/kyverno/releases/download/${KYVERNO_VERSION}/kyverno-cli_${VERSION_NUM}_linux_x86_64.tar.gz"
46+
tar -xzf "kyverno-cli_${VERSION_NUM}_linux_x86_64.tar.gz"
3747
sudo mv kyverno /usr/local/bin/
48+
rm "kyverno-cli_${VERSION_NUM}_linux_x86_64.tar.gz"
3849
kyverno version
3950
4051
- name: Validate Policies
52+
id: validate
4153
run: |
54+
set -euo pipefail
4255
echo "Validating Kyverno policies..."
4356
4457
FAILED=0
58+
VALIDATED=0
4559
4660
# Validate all policy files
47-
for policy in policies/kyverno/*.yaml; do
61+
for policy in policies/kyverno/*.yaml policies/kyverno/*.yml 2>/dev/null; do
4862
if [ -f "$policy" ]; then
4963
echo "Validating: $policy"
50-
if kyverno validate "$policy" 2>&1; then
51-
echo " OK"
52-
else
64+
# Use apply with /dev/null - validates policy syntax
65+
OUTPUT=$(kyverno apply "$policy" --resource /dev/null 2>&1 || true)
66+
if echo "$OUTPUT" | grep -qi "error\|invalid"; then
5367
echo " FAILED"
68+
echo "$OUTPUT"
5469
FAILED=$((FAILED + 1))
70+
else
71+
echo " OK"
72+
VALIDATED=$((VALIDATED + 1))
5573
fi
5674
fi
5775
done
5876
5977
# Validate template files if they exist
6078
if [ -d "templates/kyverno" ]; then
61-
for template in templates/kyverno/*.yaml; do
79+
for template in templates/kyverno/*.yaml templates/kyverno/*.yml 2>/dev/null; do
6280
if [ -f "$template" ]; then
6381
echo "Validating template: $template"
64-
if kyverno validate "$template" 2>&1; then
65-
echo " OK"
66-
else
82+
OUTPUT=$(kyverno apply "$template" --resource /dev/null 2>&1 || true)
83+
if echo "$OUTPUT" | grep -qi "error\|invalid"; then
6784
echo " FAILED"
85+
echo "$OUTPUT"
6886
FAILED=$((FAILED + 1))
87+
else
88+
echo " OK"
89+
VALIDATED=$((VALIDATED + 1))
6990
fi
7091
fi
7192
done
7293
fi
7394
95+
echo "validated=$VALIDATED" >> $GITHUB_OUTPUT
96+
echo "failed=$FAILED" >> $GITHUB_OUTPUT
97+
7498
if [ "$FAILED" -gt 0 ]; then
7599
echo "::error::$FAILED policy validation(s) failed"
76100
exit 1
77101
fi
78102
79-
echo "All policies validated successfully"
103+
echo "All $VALIDATED policies validated successfully"
80104
81105
- name: Generate Summary
82106
if: always()
83107
run: |
84-
cat >> $GITHUB_STEP_SUMMARY << 'EOF'
108+
cat >> $GITHUB_STEP_SUMMARY << EOF
85109
# Kyverno Policy Validation
86110
87-
| Policy | Purpose | Status |
88-
|--------|---------|--------|
111+
| Metric | Value |
112+
|--------|-------|
113+
| Policies Validated | ${{ steps.validate.outputs.validated || '0' }} |
114+
| Failures | ${{ steps.validate.outputs.failed || '0' }} |
115+
89116
EOF
90117
91-
for policy in policies/kyverno/*.yaml; do
118+
echo "| Policy | Purpose |" >> $GITHUB_STEP_SUMMARY
119+
echo "|--------|---------|" >> $GITHUB_STEP_SUMMARY
120+
121+
for policy in policies/kyverno/*.yaml policies/kyverno/*.yml 2>/dev/null; do
92122
if [ -f "$policy" ]; then
93123
NAME=$(basename "$policy" .yaml)
94-
PURPOSE=$(grep -A1 "policies.kyverno.io/title:" "$policy" 2>/dev/null | head -1 | sed 's/.*title:\s*//' || echo "N/A")
95-
echo "| \`$NAME\` | $PURPOSE | Validated |" >> $GITHUB_STEP_SUMMARY
124+
PURPOSE=$(grep "policies.kyverno.io/title:" "$policy" 2>/dev/null | sed 's/.*title:\s*//' | head -1 || echo "N/A")
125+
echo "| \`$NAME\` | $PURPOSE |" >> $GITHUB_STEP_SUMMARY
96126
fi
97127
done
98128

docs/adr/0012-kyverno-policies.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,32 @@ Considerations:
2626

2727
- Include Kyverno policies as an **optional feature** for users deploying to Kubernetes
2828
- Policies are stored in `policies/kyverno/` with documentation
29-
- A validation workflow (`kyverno-validate.yml`) validates policy syntax on changes
29+
- A **reusable workflow** (`kyverno-ci.yml`) allows external repos to validate their policies
30+
- An internal workflow (`kyverno-validate.yml`) validates the hub's own policy syntax on changes
3031
- Policies default to `Audit` mode (warning only) for safe adoption; users upgrade to `Enforce` when ready
3132
- The `block-pull-request-target` policy uses `Enforce` by default as it's a critical security control
3233

34+
### Reusable Workflow Usage
35+
36+
External repos can call the Kyverno CI workflow:
37+
38+
```yaml
39+
jobs:
40+
kyverno:
41+
uses: jguida941/ci-cd-hub/.github/workflows/kyverno-ci.yml@v1
42+
with:
43+
policies_dir: 'policies/kyverno'
44+
run_tests: true # Test against fixtures
45+
```
46+
47+
Available inputs:
48+
- `policies_dir`: Directory containing policy YAML files (default: `policies/kyverno`)
49+
- `templates_dir`: Directory containing policy templates (default: `templates/kyverno`)
50+
- `fixtures_dir`: Directory containing test resources (default: `fixtures/kyverno`)
51+
- `run_tests`: Run policies against fixtures (default: `false`)
52+
- `kyverno_version`: CLI version to use (default: `v1.13.0`)
53+
- `fail_on_warn`: Fail build on policy warnings (default: `false`)
54+
3355
## Alternatives Considered
3456

3557
1. **Remove Kyverno entirely:** Rejected - valuable for users with K8s deployments

0 commit comments

Comments
 (0)