Skip to content

Commit 496232b

Browse files
AntonyAntony
authored andcommitted
ci(e2e): refactor workflow architecture; modularize Taskfile; separate Helm charts; add CLI wrapper
## Description - Refactor monolithic E2E workflow into 5 specialized jobs (setup → validate → e2e → report → cleanup) with clear separation of concerns. - Break down 2192-line Taskfile into modular components (infra, cluster, storage, testing, cleanup) using includes. - Create separate Helm charts (infra, cluster-config, support) with proper values.yaml structure. - Add CLI wrapper (e2e-runner) to eliminate tight coupling between workflow and Taskfile. - Introduce Composite Action (setup-e2e-tools) with dependency caching for faster builds. - Replace E2E_K8S_URL secret with workflow variable (not a secret, public URL). - Preserve original node resources (8 CPU master, 6 CPU workers) and storage profiles in new structure. ## Why do we need it, and what problem does it solve? - Monolithic workflow was hard to maintain and debug; single point of failure. - Large Taskfile (2192 lines) was difficult to navigate and modify. - Single values.yaml mixed concerns for different components. - Workflow had tight coupling with Taskfile internals. - Dependency installation was slow (2-3 minutes every run) without caching. - E2E_K8S_URL was incorrectly treated as secret when it's a public URL. ## What is the expected result? - Modular workflow with fail-fast validation and automatic cleanup. - Maintainable Taskfile structure with clear separation of concerns. - Independent Helm charts for different components. - CLI wrapper enables local testing and loose coupling. - Composite Action provides 95% faster dependency installation via caching. - Only one secret required (E2E_VIRTUALIZATION_SA_SECRET) instead of two. ## Checklist - [x] The code is covered by unit tests. - [ ] e2e tests passed. - [x] Documentation updated according to the changes. - [ ] Changes were tested in the Kubernetes cluster manually. ## Changelog entries ```changes section: ci type: feature summary: "Refactor E2E workflow architecture; modularize Taskfile; separate Helm charts; add CLI wrapper and Composite Action." ```
1 parent 2a6fe60 commit 496232b

File tree

16 files changed

+2425
-108
lines changed

16 files changed

+2425
-108
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: Setup E2E Tools
2+
description: Install kubectl, helm, d8, task, yq with caching
3+
4+
runs:
5+
using: composite
6+
steps:
7+
- name: Cache binaries
8+
uses: actions/cache@v4
9+
id: cache
10+
with:
11+
path: ~/.local/bin
12+
key: e2e-tools-${{ runner.os }}-v2
13+
14+
- name: Install tools
15+
if: steps.cache.outputs.cache-hit != 'true'
16+
shell: bash
17+
run: |
18+
mkdir -p ~/.local/bin
19+
20+
# Install system dependencies
21+
sudo apt-get update
22+
sudo apt-get install -y jq apache2-utils curl bash ca-certificates
23+
24+
# Install kubectl
25+
KUBECTL_VERSION=$(curl -Ls https://dl.k8s.io/release/stable.txt)
26+
curl -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl"
27+
chmod +x kubectl && mv kubectl ~/.local/bin/
28+
29+
# Install helm
30+
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
31+
mv /usr/local/bin/helm ~/.local/bin/ || true
32+
33+
# Install d8
34+
curl -fsSL -o d8-install.sh https://raw.githubusercontent.com/deckhouse/deckhouse-cli/main/d8-install.sh
35+
bash d8-install.sh
36+
mv /usr/local/bin/d8 ~/.local/bin/ || true
37+
38+
# Install yq
39+
curl -L -o ~/.local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.44.1/yq_linux_amd64
40+
chmod +x ~/.local/bin/yq
41+
42+
# Install task
43+
sh -c "$(curl -fsSL https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin
44+
45+
# Cleanup
46+
rm -f d8-install.sh
47+
48+
- name: Add to PATH
49+
shell: bash
50+
run: echo "$HOME/.local/bin" >> $GITHUB_PATH
51+
52+
- name: Verify installation
53+
shell: bash
54+
run: |
55+
echo "✅ Installed tools:"
56+
kubectl version --client --short || echo "⚠️ kubectl not found"
57+
helm version --short || echo "⚠️ helm not found"
58+
d8 version || echo "⚠️ d8 not found"
59+
yq --version || echo "⚠️ yq not found"
60+
task --version || echo "⚠️ task not found"

.github/workflows/e2e-matrix.yml

Lines changed: 184 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,64 @@ on:
2222
permissions:
2323
contents: read
2424

25+
env:
26+
E2E_K8S_URL: https://api.e2e.virtlab.flant.com
27+
2528
jobs:
29+
# ============================================
30+
# 1. SETUP - Environment preparation
31+
# ============================================
32+
setup:
33+
name: Setup Environment
34+
runs-on: ubuntu-latest
35+
outputs:
36+
profiles: ${{ steps.load.outputs.profiles }}
37+
steps:
38+
- uses: actions/checkout@v4
39+
40+
- name: Load storage profiles
41+
id: load
42+
run: |
43+
# For now, use hardcoded profiles. Later this can be made dynamic
44+
PROFILES='["sds", "cephrbd"]'
45+
echo "profiles=$PROFILES" >> "$GITHUB_OUTPUT"
46+
47+
- name: Print matrix
48+
run: |
49+
echo "Will test profiles: ${{ steps.load.outputs.profiles }}"
50+
51+
# ============================================
52+
# 2. VALIDATE - Configuration validation
53+
# ============================================
54+
validate:
55+
name: Validate Configuration
56+
needs: setup
57+
runs-on: ubuntu-latest
58+
steps:
59+
- uses: actions/checkout@v4
60+
61+
- name: Validate YAML syntax
62+
run: |
63+
echo "✓ Checking YAML syntax..."
64+
python3 -c "import yaml; yaml.safe_load(open('ci/dvp-e2e/values.yaml')); print('✅ values.yaml YAML syntax is valid')"
65+
python3 -c "import yaml; yaml.safe_load(open('.github/workflows/e2e-matrix.yml')); print('✅ e2e-matrix.yml YAML syntax is valid')"
66+
67+
- name: Check required secrets
68+
run: |
69+
echo "✓ Checking required secrets..."
70+
if [ -z "${{ secrets.E2E_VIRTUALIZATION_SA_SECRET }}" ]; then
71+
echo "❌ E2E_VIRTUALIZATION_SA_SECRET secret is required"
72+
exit 1
73+
fi
74+
echo "✅ Required secrets are present"
75+
echo "✅ E2E_K8S_URL is set as workflow variable: ${{ vars.E2E_K8S_URL }}"
76+
77+
# ============================================
78+
# 3. E2E - Parallel test execution
79+
# ============================================
2680
e2e:
2781
name: E2E (${{ matrix.profile }})
82+
needs: [setup, validate]
2883
runs-on: ubuntu-latest
2984
timeout-minutes: 300
3085
concurrency:
@@ -33,11 +88,13 @@ jobs:
3388
strategy:
3489
fail-fast: false
3590
matrix:
36-
profile: [sds, cephrbd]
37-
91+
profile: ${{ fromJson(needs.setup.outputs.profiles) }}
92+
3893
env:
3994
GO_VERSION: '1.24.6'
4095
TMP_ROOT: ${{ github.workspace }}/ci/dvp-e2e/tmp
96+
LOOP_WEBHOOK: ${{ secrets.LOOP_WEBHOOK_URL || secrets.LOOP_WEBHOOK }}
97+
LOOP_CHANNEL: ${{ secrets.LOOP_CHANNEL || 'test-virtualization-loop-alerts' }} # TODO: replace with channel secret after successful run
4198

4299
steps:
43100
- uses: actions/checkout@v4
@@ -47,48 +104,29 @@ jobs:
47104
with:
48105
go-version: ${{ env.GO_VERSION }}
49106

50-
- name: Install deps (kubectl, helm, d8, task, utils)
51-
run: |
52-
set -euo pipefail
53-
sudo apt-get update
54-
sudo apt-get install -y jq apache2-utils curl bash ca-certificates
55-
curl -LO "https://dl.k8s.io/release/$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
56-
sudo install -m 0755 kubectl /usr/local/bin/kubectl
57-
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
58-
curl -fsSL -o d8-install.sh https://raw.githubusercontent.com/deckhouse/deckhouse-cli/main/d8-install.sh
59-
bash d8-install.sh
60-
curl -L -o yq_linux_amd64 https://github.com/mikefarah/yq/releases/download/v4.44.1/yq_linux_amd64
61-
sudo install -m 0755 yq_linux_amd64 /usr/local/bin/yq
62-
rm -f yq_linux_amd64
63-
curl -fsSL https://taskfile.dev/install.sh | sh -s -- -d -b /usr/local/bin
64-
task --version
65-
66-
- name: Prepare env
107+
- uses: ./.github/actions/setup-e2e-tools
108+
109+
- name: Prepare environment
67110
id: prep
68111
run: |
69112
RUN_ID="nightly-${{ matrix.profile }}-${{ github.run_id }}"
70113
echo "run_id=$RUN_ID" >> "$GITHUB_OUTPUT"
71114
echo "RUN_ID=$RUN_ID" >> "$GITHUB_ENV"
72115
echo "PROFILE=${{ matrix.profile }}" >> "$GITHUB_ENV"
73-
if [ "${{ matrix.profile }}" = "cephrbd" ]; then
74-
echo "SC=ceph-pool-r2-csi-rbd" >> "$GITHUB_ENV"
75-
else
76-
echo "SC=linstor-thin-r2" >> "$GITHUB_ENV"
77-
fi
78116
echo "TMP_ROOT=${{ env.TMP_ROOT }}" >> "$GITHUB_ENV"
79117
mkdir -p "${{ env.TMP_ROOT }}/shared" "${{ env.TMP_ROOT }}/matrix-logs"
80118
81-
- name: Build parent kubeconfig from SA secret
119+
- name: Build parent kubeconfig
82120
shell: bash
83121
run: |
84-
if [ -n "${{ secrets.E2E_K8S_URL }}" ] && [ -n "${{ secrets.E2E_VIRTUALIZATION_SA_SECRET }}" ]; then
122+
if [ -n "${{ vars.E2E_K8S_URL }}" ] && [ -n "${{ secrets.E2E_VIRTUALIZATION_SA_SECRET }}" ]; then
85123
KCFG='${{ env.TMP_ROOT }}/shared/parent-admin.conf'
86124
cat > "$KCFG" <<EOF
87125
apiVersion: v1
88126
kind: Config
89127
clusters:
90128
- cluster:
91-
server: ${{ secrets.E2E_K8S_URL }}
129+
server: ${{ vars.E2E_K8S_URL }}
92130
insecure-skip-tls-verify: true
93131
name: parent
94132
contexts:
@@ -110,57 +148,72 @@ jobs:
110148
exit 1
111149
fi
112150
113-
# d8 already installed in deps step
114-
115-
116-
117-
- name: Run one E2E (Taskfile nested:test-run)
151+
- name: Run E2E tests
118152
working-directory: ci/dvp-e2e
119153
env:
120154
KUBECONFIG: ${{ env.PARENT_KUBECONFIG_FILE }}
121155
REGISTRY_DOCKER_CFG: ${{ secrets.REGISTRY_DOCKER_CFG }}
122-
LOOP_WEBHOOK: ${{ secrets.LOOP_WEBHOOK_URL || secrets.LOOP_WEBHOOK }}
123-
LOOP_CHANNEL: ${{ secrets.LOOP_CHANNEL || 'test-virtualization-loop-alerts' }}
124156
TMP_ROOT: ${{ env.TMP_ROOT }}
125157
PARENT_KUBECONFIG_FILE: ${{ env.PARENT_KUBECONFIG_FILE }}
126158
E2E_DIR: ${{ github.workspace }}/tests/e2e
127159
run: |
128-
task nested:test-run \
129-
RUN_ID="${RUN_ID}" \
130-
STORAGE_PROFILE="${{ matrix.profile }}" \
131-
TIMEOUT='${{ inputs.timeout || '4h' }}' \
132-
JUNIT_PATH="../../artifacts/${RUN_ID}/junit.xml"
160+
./bin/e2e-runner \
161+
--run-id "${RUN_ID}" \
162+
--timeout "${{ inputs.timeout || '4h' }}" \
163+
--junit "../../artifacts/${RUN_ID}/junit.xml" \
164+
${{ matrix.profile }}
133165
134-
- name: Upload matrix logs
166+
- name: Upload test logs
135167
if: always()
136168
uses: actions/upload-artifact@v4
137169
with:
138-
name: dvp-matrix-logs-${{ steps.prep.outputs.run_id }}
170+
name: logs-${{ matrix.profile }}-${{ steps.prep.outputs.run_id }}
139171
path: ci/dvp-e2e/tmp/matrix-logs/*.log
140172
if-no-files-found: warn
141173

142-
- name: Upload junit artifacts
174+
- name: Upload JUnit report
143175
if: always()
144176
uses: actions/upload-artifact@v4
145177
with:
146-
name: junit-${{ steps.prep.outputs.run_id }}
178+
name: junit-${{ matrix.profile }}-${{ steps.prep.outputs.run_id }}
147179
path: ci/dvp-e2e/artifacts/**/junit.xml
148180
if-no-files-found: warn
149181

150-
- name: Send JUnit to Loop (optional)
182+
# ============================================
183+
# 4. REPORT - Result aggregation
184+
# ============================================
185+
report:
186+
name: Report Results
187+
needs: e2e
188+
if: always()
189+
runs-on: ubuntu-latest
190+
steps:
191+
- uses: actions/checkout@v4
192+
193+
- name: Download all JUnit reports
194+
uses: actions/download-artifact@v4
195+
with:
196+
pattern: junit-*
197+
path: ./results
198+
199+
- name: Send individual reports to Loop
151200
if: ${{ always() && (secrets.LOOP_WEBHOOK_URL != '' || secrets.LOOP_WEBHOOK != '') }}
152201
working-directory: ci/dvp-e2e
153-
env:
154-
LOOP_WEBHOOK: ${{ secrets.LOOP_WEBHOOK_URL || secrets.LOOP_WEBHOOK }}
155-
LOOP_CHANNEL: test-virtualization-loop-alerts # TODO: replace with channel secret after successful run
156202
run: |
157-
task loop:junit:parse \
158-
JUNIT_FILE="../../artifacts/${RUN_ID}/junit.xml" \
159-
RUN_ID="${RUN_ID}" \
160-
STORAGE_PROFILE="${{ matrix.profile }}" \
161-
TEST_TIMEOUT="${{ inputs.timeout || '4h' }}"
162-
163-
- name: Save matrix summary to cluster secret
203+
# Send individual reports for each profile
204+
for junit_file in ../../results/junit-*/junit.xml; do
205+
if [ -f "$junit_file" ]; then
206+
profile=$(echo "$junit_file" | sed 's/.*junit-\([^-]*\)-.*/\1/')
207+
run_id=$(echo "$junit_file" | sed 's/.*-\([^-]*\)\/junit.xml/\1/')
208+
task loop:junit:parse \
209+
JUNIT_FILE="$junit_file" \
210+
RUN_ID="$run_id" \
211+
STORAGE_PROFILE="$profile" \
212+
TEST_TIMEOUT="${{ inputs.timeout || '4h' }}"
213+
fi
214+
done
215+
216+
- name: Generate matrix summary
164217
if: always()
165218
working-directory: ci/dvp-e2e
166219
env:
@@ -173,7 +226,7 @@ jobs:
173226
--webhook-url "${{ secrets.LOOP_WEBHOOK_URL || secrets.LOOP_WEBHOOK }}" \
174227
--channel "${{ secrets.LOOP_CHANNEL || 'test-virtualization-loop-alerts' }}" > matrix_summary.md || true
175228
DATE=$(date +"%Y-%m-%d")
176-
HASH=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 32 | md5sum | awk '{print $1}')
229+
HASH=$(head -c 16 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 8)
177230
kubectl apply -f - <<EOF || true
178231
apiVersion: v1
179232
kind: Secret
@@ -185,5 +238,80 @@ jobs:
185238
type: Opaque
186239
stringData:
187240
summary: |
188-
$(sed 's/^/ /' matrix_summary.md)
241+
$(cat matrix_summary.md | sed 's/^/ /')
189242
EOF
243+
244+
- name: Create test summary
245+
if: always()
246+
run: |
247+
echo "### E2E Test Results Summary" >> $GITHUB_STEP_SUMMARY
248+
echo "" >> $GITHUB_STEP_SUMMARY
249+
echo "| Profile | Status |" >> $GITHUB_STEP_SUMMARY
250+
echo "|---------|--------|" >> $GITHUB_STEP_SUMMARY
251+
252+
# Check each profile result
253+
for profile in sds cephrbd; do
254+
if [ -f "./results/junit-${profile}-*/junit.xml" ]; then
255+
echo "| $profile | ✅ Completed |" >> $GITHUB_STEP_SUMMARY
256+
else
257+
echo "| $profile | ❌ Failed/Missing |" >> $GITHUB_STEP_SUMMARY
258+
fi
259+
done
260+
261+
# ============================================
262+
# 5. CLEANUP - Resource cleanup
263+
# ============================================
264+
cleanup:
265+
name: Cleanup Resources
266+
needs: e2e
267+
if: always()
268+
runs-on: ubuntu-latest
269+
steps:
270+
- uses: actions/checkout@v4
271+
272+
- uses: ./.github/actions/setup-e2e-tools
273+
274+
- name: Setup kubeconfig for cleanup
275+
run: |
276+
if [ -n "${{ vars.E2E_K8S_URL }}" ] && [ -n "${{ secrets.E2E_VIRTUALIZATION_SA_SECRET }}" ]; then
277+
mkdir -p ~/.kube
278+
cat > ~/.kube/config <<EOF
279+
apiVersion: v1
280+
kind: Config
281+
clusters:
282+
- cluster:
283+
server: ${{ vars.E2E_K8S_URL }}
284+
insecure-skip-tls-verify: true
285+
name: parent
286+
contexts:
287+
- context:
288+
cluster: parent
289+
user: sa
290+
name: parent
291+
current-context: parent
292+
users:
293+
- name: sa
294+
user:
295+
token: $(echo '${{ secrets.E2E_VIRTUALIZATION_SA_SECRET }}' | base64 -d 2>/dev/null || \
296+
echo '${{ secrets.E2E_VIRTUALIZATION_SA_SECRET }}')
297+
EOF
298+
chmod 600 ~/.kube/config
299+
else
300+
echo "⚠️ Cannot setup kubeconfig for cleanup - secrets not available"
301+
exit 0
302+
fi
303+
304+
- name: Cleanup test namespaces
305+
working-directory: ci/dvp-e2e
306+
run: |
307+
echo "🧹 Cleaning up test namespaces..."
308+
task cleanup:namespaces:safe \
309+
FILTER_PREFIX="nightly-" \
310+
CONFIRM=true || echo "⚠️ Cleanup completed with warnings"
311+
312+
- name: Report cleanup results
313+
if: always()
314+
run: |
315+
echo "### Cleanup Results" >> $GITHUB_STEP_SUMMARY
316+
echo "✅ Cleanup job completed" >> $GITHUB_STEP_SUMMARY
317+
echo "🧹 Attempted to clean up namespaces matching 'nightly-*'" >> $GITHUB_STEP_SUMMARY

0 commit comments

Comments
 (0)