forked from wazuh/wazuh-docker
489 lines
21 KiB
YAML
489 lines
21 KiB
YAML
run-name: >-
|
|
${{ github.event_name == 'workflow_dispatch'
|
|
&& format('Docker Integration Test - Manual {0} on {1}', inputs.deployment_type, inputs.pr_head_ref)
|
|
|| format('Docker Integration Test - #{0} {1}', github.event.issue.number, github.event.issue.title) }}
|
|
name: PR Check - Docker Integration Tests
|
|
|
|
on:
|
|
issue_comment:
|
|
types: [created]
|
|
workflow_dispatch:
|
|
inputs:
|
|
pr_head_ref:
|
|
description: 'Branch of wazuh-docker to test'
|
|
required: true
|
|
type: string
|
|
automation_reference:
|
|
description: 'Branch of wazuh-automation to use'
|
|
required: false
|
|
default: 'main'
|
|
type: string
|
|
deployment_type:
|
|
description: 'Deployment type to test'
|
|
required: true
|
|
type: choice
|
|
options:
|
|
- single-node
|
|
- multi-node
|
|
- both
|
|
|
|
permissions:
|
|
id-token: write
|
|
contents: read
|
|
pull-requests: write
|
|
issues: write
|
|
checks: write
|
|
|
|
env:
|
|
AUTOMATION_REFERENCE: ${{ inputs.automation_reference || 'main' }}
|
|
ALLOCATOR_PATH: /tmp/allocator_instance
|
|
REGION: us-east-1
|
|
|
|
jobs:
|
|
# -------------------------------------------------------------------------
|
|
# Job 1: Parse PR info and determine which deployment(s) to test
|
|
#
|
|
# Available commands (checked longest-match first to avoid substring collision):
|
|
# /test-docker-single — test single-node deployment
|
|
# /test-docker-multi — test multi-node deployment
|
|
# /test-docker — test both single-node and multi-node
|
|
# -------------------------------------------------------------------------
|
|
get_pr_info:
|
|
if: |
|
|
github.event_name == 'issue_comment' &&
|
|
github.event.issue.pull_request &&
|
|
github.event.issue.state == 'open' &&
|
|
!github.event.issue.draft &&
|
|
(contains(github.event.comment.body, '/test-docker-single') ||
|
|
contains(github.event.comment.body, '/test-docker-multi') ||
|
|
contains(github.event.comment.body, '/test-docker'))
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
pr_number: ${{ steps.pr_data.outputs.pr_number }}
|
|
pr_head_ref: ${{ steps.pr_data.outputs.pr_head_ref }}
|
|
pr_head_sha: ${{ steps.pr_data.outputs.pr_head_sha }}
|
|
check_run_id: ${{ steps.create_check.outputs.result }}
|
|
deployment_matrix: ${{ steps.parse_command.outputs.deployment_matrix }}
|
|
check_name: ${{ steps.parse_command.outputs.check_name }}
|
|
|
|
steps:
|
|
- name: React to comment
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
await github.rest.reactions.createForIssueComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
comment_id: context.payload.comment.id,
|
|
content: 'rocket'
|
|
});
|
|
|
|
- name: Extract PR data
|
|
id: pr_data
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
PR_NUMBER="${{ github.event.issue.number }}"
|
|
PR_DATA=$(gh api repos/${{ github.repository }}/pulls/${PR_NUMBER})
|
|
PR_HEAD_REF=$(echo "$PR_DATA" | jq -r '.head.ref')
|
|
PR_HEAD_SHA=$(echo "$PR_DATA" | jq -r '.head.sha')
|
|
echo "pr_number=${PR_NUMBER}" >> $GITHUB_OUTPUT
|
|
echo "pr_head_ref=${PR_HEAD_REF}" >> $GITHUB_OUTPUT
|
|
echo "pr_head_sha=${PR_HEAD_SHA}" >> $GITHUB_OUTPUT
|
|
|
|
- name: Parse command and set deployment metadata
|
|
id: parse_command
|
|
run: |
|
|
COMMENT_BODY="${{ github.event.comment.body }}"
|
|
# Check longest match first to avoid /test-docker matching /test-docker-single
|
|
if echo "$COMMENT_BODY" | grep -q '/test-docker-single'; then
|
|
echo 'deployment_matrix=["single-node"]' >> $GITHUB_OUTPUT
|
|
echo 'check_name=Docker Integration Check (Single-Node)' >> $GITHUB_OUTPUT
|
|
elif echo "$COMMENT_BODY" | grep -q '/test-docker-multi'; then
|
|
echo 'deployment_matrix=["multi-node"]' >> $GITHUB_OUTPUT
|
|
echo 'check_name=Docker Integration Check (Multi-Node)' >> $GITHUB_OUTPUT
|
|
elif echo "$COMMENT_BODY" | grep -q '/test-docker'; then
|
|
echo 'deployment_matrix=["single-node","multi-node"]' >> $GITHUB_OUTPUT
|
|
echo 'check_name=Docker Integration Check' >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
- name: Create check run
|
|
id: create_check
|
|
uses: actions/github-script@v7
|
|
env:
|
|
HEAD_SHA: ${{ steps.pr_data.outputs.pr_head_sha }}
|
|
CHECK_NAME: ${{ steps.parse_command.outputs.check_name }}
|
|
COMMENT_BODY: ${{ github.event.comment.body }}
|
|
with:
|
|
script: |
|
|
const { data: check } = await github.rest.checks.create({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
name: process.env.CHECK_NAME,
|
|
head_sha: process.env.HEAD_SHA,
|
|
status: 'in_progress',
|
|
started_at: new Date().toISOString(),
|
|
details_url: `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
|
|
output: {
|
|
title: `🔨 Running ${process.env.CHECK_NAME}...`,
|
|
summary: `Triggered by comment: \`${process.env.COMMENT_BODY}\``,
|
|
text: 'Allocating instance and running Docker integration tests'
|
|
}
|
|
});
|
|
console.log('Check run created:', check.id);
|
|
return check.id;
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Job 2: Resolve context (pr_head_ref + deployment matrix) for both triggers.
|
|
# Mirrors the role of build_tools in check_integration_tools.yaml.
|
|
# -------------------------------------------------------------------------
|
|
prepare:
|
|
needs: [get_pr_info]
|
|
if: |
|
|
always() &&
|
|
(needs.get_pr_info.result == 'success' || github.event_name == 'workflow_dispatch')
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
pr_head_ref: ${{ steps.ctx.outputs.pr_head_ref }}
|
|
deployment_matrix: ${{ steps.ctx.outputs.deployment_matrix }}
|
|
|
|
steps:
|
|
- name: Resolve context
|
|
id: ctx
|
|
run: |
|
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
echo "pr_head_ref=${{ inputs.pr_head_ref }}" >> $GITHUB_OUTPUT
|
|
DEPLOY_TYPE="${{ inputs.deployment_type }}"
|
|
if [ "$DEPLOY_TYPE" = "both" ]; then
|
|
echo 'deployment_matrix=["single-node","multi-node"]' >> $GITHUB_OUTPUT
|
|
else
|
|
echo "deployment_matrix=[\"${DEPLOY_TYPE}\"]" >> $GITHUB_OUTPUT
|
|
fi
|
|
else
|
|
echo "pr_head_ref=${{ needs.get_pr_info.outputs.pr_head_ref }}" >> $GITHUB_OUTPUT
|
|
echo "deployment_matrix=${{ needs.get_pr_info.outputs.deployment_matrix }}" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Job 3: For each deployment type — provision VM, deploy Docker stack, test,
|
|
# collect results, and clean up.
|
|
# -------------------------------------------------------------------------
|
|
docker_test:
|
|
needs: [get_pr_info, prepare]
|
|
if: always() && needs.prepare.result == 'success'
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
deployment_type: ${{ fromJSON(needs.prepare.outputs.deployment_matrix) }}
|
|
|
|
steps:
|
|
# -----------------------------------------------------------------------
|
|
# Setup
|
|
# -----------------------------------------------------------------------
|
|
- name: Checkout wazuh-automation
|
|
uses: actions/checkout@v4
|
|
with:
|
|
repository: wazuh/wazuh-automation
|
|
ref: ${{ env.AUTOMATION_REFERENCE }}
|
|
token: ${{ secrets.GH_CLONE_TOKEN }}
|
|
path: wazuh-automation
|
|
|
|
- name: Checkout wazuh-docker PR branch
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ needs.prepare.outputs.pr_head_ref }}
|
|
path: wazuh-docker
|
|
|
|
- name: Set up Python 3.12
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: '3.12'
|
|
|
|
- name: Install requirements
|
|
run: |
|
|
pip install -r wazuh-automation/deployability/deps/requirements.txt
|
|
pip install -r wazuh-automation/integration-test-module/requirements.txt
|
|
pip install -e wazuh-automation/integration-test-module/
|
|
|
|
- name: Configure AWS credentials
|
|
uses: aws-actions/configure-aws-credentials@v4
|
|
with:
|
|
role-to-assume: ${{ secrets.AWS_IAM_DOCKER_ROLE }}
|
|
role-session-name: docker-test-${{ github.run_id }}-${{ matrix.deployment_type }}
|
|
aws-region: ${{ env.REGION }}
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Provision: allocate VM and extract SSH credentials
|
|
# -----------------------------------------------------------------------
|
|
- name: Allocate instance
|
|
id: allocate
|
|
run: |
|
|
mkdir -p ${{ env.ALLOCATOR_PATH }}
|
|
python3 wazuh-automation/deployability/modules/allocation/main.py \
|
|
--action create \
|
|
--provider aws \
|
|
--size large \
|
|
--composite-name ubuntu-24-amd64 \
|
|
--working-dir ${{ env.ALLOCATOR_PATH }} \
|
|
--track-output ${{ env.ALLOCATOR_PATH }}/track.yml \
|
|
--inventory-output ${{ env.ALLOCATOR_PATH }}/inventory.yml \
|
|
--instance-name gha_${{ github.run_id }}_docker_${{ matrix.deployment_type }} \
|
|
--label-team devops \
|
|
--label-termination-date 1d
|
|
|
|
sed -n '/hosts:/,/^[^ ]/p' ${{ env.ALLOCATOR_PATH }}/inventory.yml \
|
|
| grep "ansible_" \
|
|
| sed 's/^[ ]*//g' \
|
|
> ${{ env.ALLOCATOR_PATH }}/inventory_vars_raw.yml
|
|
sed 's/: */=/g' ${{ env.ALLOCATOR_PATH }}/inventory_vars_raw.yml \
|
|
> ${{ env.ALLOCATOR_PATH }}/inventory_vars.yml
|
|
sed -i 's/-o StrictHostKeyChecking=no/"-o StrictHostKeyChecking=no"/g' \
|
|
${{ env.ALLOCATOR_PATH }}/inventory_vars.yml
|
|
|
|
- name: Set SSH credentials from inventory
|
|
run: |
|
|
find ${{ env.ALLOCATOR_PATH }} -name '*-key-*' -exec chmod 600 {} \;
|
|
source ${{ env.ALLOCATOR_PATH }}/inventory_vars.yml
|
|
echo "SSH_HOST=$ansible_host" >> $GITHUB_ENV
|
|
echo "SSH_PORT=$ansible_port" >> $GITHUB_ENV
|
|
echo "SSH_USER=$ansible_user" >> $GITHUB_ENV
|
|
echo "SSH_KEY=$ansible_ssh_private_key_file" >> $GITHUB_ENV
|
|
|
|
- name: Set SSH/SCP helper env vars
|
|
run: |
|
|
echo "SSH_OPTS=-o StrictHostKeyChecking=no -o ServerAliveInterval=60 -o ServerAliveCountMax=20 -p ${{ env.SSH_PORT }} -i ${{ env.SSH_KEY }}" >> $GITHUB_ENV
|
|
echo "SCP_OPTS=-o StrictHostKeyChecking=no -P ${{ env.SSH_PORT }} -i ${{ env.SSH_KEY }}" >> $GITHUB_ENV
|
|
echo "REMOTE=${{ env.SSH_USER }}@${{ env.SSH_HOST }}" >> $GITHUB_ENV
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Install Docker CE on the remote VM
|
|
# -----------------------------------------------------------------------
|
|
- name: Install Docker CE
|
|
run: |
|
|
ssh ${{ env.SSH_OPTS }} "${{ env.REMOTE }}" "
|
|
curl -fsSL https://get.docker.com | sudo sh
|
|
sudo systemctl enable --now docker
|
|
"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Deploy: copy wazuh-docker and start the stack
|
|
# -----------------------------------------------------------------------
|
|
- name: Copy wazuh-docker to VM
|
|
run: |
|
|
scp ${{ env.SCP_OPTS }} -r wazuh-docker "${{ env.REMOTE }}:/tmp/wazuh-docker"
|
|
|
|
- name: Start Docker Compose
|
|
run: |
|
|
DEPLOYMENT="${{ matrix.deployment_type }}"
|
|
ssh ${{ env.SSH_OPTS }} "${{ env.REMOTE }}" "
|
|
cd /tmp/wazuh-docker/${DEPLOYMENT}
|
|
sudo docker compose up -d 2>&1 | tee /tmp/docker-compose-up.log
|
|
"
|
|
|
|
- name: Wait for containers healthy
|
|
timeout-minutes: 15
|
|
run: |
|
|
DEPLOYMENT="${{ matrix.deployment_type }}"
|
|
ssh ${{ env.SSH_OPTS }} "${{ env.REMOTE }}" "
|
|
cd /tmp/wazuh-docker/${DEPLOYMENT}
|
|
echo 'Waiting for all containers to be healthy...'
|
|
for i in \$(seq 1 90); do
|
|
NOT_HEALTHY=\$(sudo docker compose ps 2>/dev/null \
|
|
| tail -n +2 \
|
|
| grep -v 'nginx' \
|
|
| grep -vcE '(healthy|\(healthy\))')
|
|
if [ \"\$NOT_HEALTHY\" -eq 0 ]; then
|
|
echo \"All containers healthy after \${i} x 10s attempts\"
|
|
sudo docker compose ps
|
|
exit 0
|
|
fi
|
|
echo \" attempt \$i: \$NOT_HEALTHY container(s) not yet healthy\"
|
|
sleep 10
|
|
done
|
|
echo 'ERROR: containers not healthy after 15 minutes'
|
|
sudo docker compose ps
|
|
exit 1
|
|
"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Run integration tests
|
|
# -----------------------------------------------------------------------
|
|
- name: Run tests
|
|
id: run_tests
|
|
continue-on-error: true
|
|
run: |
|
|
DEPLOYMENT="${{ matrix.deployment_type }}"
|
|
test_runner \
|
|
--test-type "docker-${DEPLOYMENT}" \
|
|
--deployment-type "docker-${DEPLOYMENT}" \
|
|
--ssh-host "${{ env.SSH_HOST }}" \
|
|
--ssh-port "${{ env.SSH_PORT }}" \
|
|
--ssh-key-path "${{ env.SSH_KEY }}" \
|
|
--ssh-username "${{ env.SSH_USER }}" \
|
|
--log-level INFO \
|
|
--output github \
|
|
--output-file "test-results-docker-${DEPLOYMENT}.github"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Collect logs on failure
|
|
# -----------------------------------------------------------------------
|
|
- name: Collect Docker logs on failure
|
|
if: failure() || steps.run_tests.outcome == 'failure'
|
|
run: |
|
|
DEPLOYMENT="${{ matrix.deployment_type }}"
|
|
ssh ${{ env.SSH_OPTS }} "${{ env.REMOTE }}" "
|
|
cd /tmp/wazuh-docker/${DEPLOYMENT}
|
|
sudo docker compose logs --no-color 2>&1
|
|
" > docker-logs-${DEPLOYMENT}.txt || true
|
|
|
|
- name: Upload Docker logs
|
|
if: failure() || steps.run_tests.outcome == 'failure'
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: docker-logs-${{ matrix.deployment_type }}-${{ github.run_id }}
|
|
path: docker-logs-*.txt
|
|
retention-days: 7
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Reporting
|
|
# -----------------------------------------------------------------------
|
|
- name: Create step summary
|
|
if: always()
|
|
run: |
|
|
DEPLOYMENT="${{ matrix.deployment_type }}"
|
|
echo "## Docker Integration Test Results — ${DEPLOYMENT}" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
if [ -f "test-results-docker-${DEPLOYMENT}.github" ]; then
|
|
cat "test-results-docker-${DEPLOYMENT}.github" >> $GITHUB_STEP_SUMMARY
|
|
else
|
|
echo "No test results file found." >> $GITHUB_STEP_SUMMARY
|
|
fi
|
|
|
|
- name: Post PR comment with results
|
|
if: always() && github.event_name == 'issue_comment'
|
|
uses: actions/github-script@v7
|
|
env:
|
|
DEPLOYMENT: ${{ matrix.deployment_type }}
|
|
RUN_OUTCOME: ${{ steps.run_tests.outcome }}
|
|
with:
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
script: |
|
|
const fs = require('fs');
|
|
const deployment = process.env.DEPLOYMENT;
|
|
const outcome = process.env.RUN_OUTCOME;
|
|
const marker = `<!-- docker-integration-check-${deployment} -->`;
|
|
|
|
let body = `${marker}\n## Docker Integration Tests — \`${deployment}\`\n\n`;
|
|
body += outcome === 'success'
|
|
? '✅ **All tests passed!**\n\n'
|
|
: '❌ **Some tests failed**\n\n';
|
|
|
|
const resultsFile = `test-results-docker-${deployment}.github`;
|
|
try {
|
|
if (fs.existsSync(resultsFile)) {
|
|
body += '### Results\n\n' + fs.readFileSync(resultsFile, 'utf8') + '\n\n';
|
|
}
|
|
} catch (e) {
|
|
console.log('Could not read results file:', e.message);
|
|
}
|
|
body += `- **Workflow:** [View Details](${context.payload.repository.html_url}/actions/runs/${context.runId})\n`;
|
|
|
|
const { data: comments } = await github.rest.issues.listComments({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: context.issue.number,
|
|
});
|
|
|
|
const existing = comments.find(c =>
|
|
c.user.type === 'Bot' && c.body.includes(marker)
|
|
);
|
|
|
|
if (existing) {
|
|
await github.rest.issues.updateComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
comment_id: existing.id,
|
|
body: body,
|
|
});
|
|
} else {
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: context.issue.number,
|
|
body: body,
|
|
});
|
|
}
|
|
|
|
- name: Upload test results
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: test-results-docker-${{ matrix.deployment_type }}-${{ github.run_id }}
|
|
path: test-results-docker-${{ matrix.deployment_type }}.github
|
|
retention-days: 7
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Cleanup: always stop stack and deallocate VM
|
|
# -----------------------------------------------------------------------
|
|
- name: Stop Docker Compose
|
|
if: always()
|
|
run: |
|
|
DEPLOYMENT="${{ matrix.deployment_type }}"
|
|
ssh ${{ env.SSH_OPTS }} "${{ env.REMOTE }}" "
|
|
cd /tmp/wazuh-docker/${DEPLOYMENT} && sudo docker compose down -v || true
|
|
" || true
|
|
|
|
- name: Configure AWS credentials for cleanup
|
|
if: always()
|
|
uses: aws-actions/configure-aws-credentials@v4
|
|
with:
|
|
role-to-assume: ${{ secrets.AWS_IAM_DOCKER_ROLE }}
|
|
role-session-name: docker-cleanup-${{ github.run_id }}-${{ matrix.deployment_type }}
|
|
aws-region: ${{ env.REGION }}
|
|
|
|
- name: Deallocate instance
|
|
if: always()
|
|
run: |
|
|
python3 wazuh-automation/deployability/modules/allocation/main.py \
|
|
--action delete \
|
|
--track-output ${{ env.ALLOCATOR_PATH }}/track.yml
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Job 4: Update the GitHub check run (issue_comment trigger only)
|
|
# -------------------------------------------------------------------------
|
|
update_check:
|
|
needs: [get_pr_info, prepare, docker_test]
|
|
if: always() && github.event_name == 'issue_comment' && needs.get_pr_info.result == 'success'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Update check run
|
|
uses: actions/github-script@v7
|
|
env:
|
|
DOCKER_RESULT: ${{ needs.docker_test.result }}
|
|
CHECK_NAME: ${{ needs.get_pr_info.outputs.check_name }}
|
|
CHECK_RUN_ID: ${{ needs.get_pr_info.outputs.check_run_id }}
|
|
with:
|
|
script: |
|
|
const result = process.env.DOCKER_RESULT;
|
|
const conclusionMap = {
|
|
success: { conclusion: 'success', icon: '✅', summary: 'All Docker integration tests passed.' },
|
|
failure: { conclusion: 'failure', icon: '❌', summary: 'One or more Docker integration tests failed.' },
|
|
cancelled: { conclusion: 'cancelled', icon: '⏹️', summary: 'Workflow was cancelled.' },
|
|
};
|
|
const { conclusion, icon, summary } = conclusionMap[result] ?? conclusionMap.failure;
|
|
const label = conclusion.charAt(0).toUpperCase() + conclusion.slice(1);
|
|
await github.rest.checks.update({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
check_run_id: parseInt(process.env.CHECK_RUN_ID),
|
|
status: 'completed',
|
|
conclusion,
|
|
completed_at: new Date().toISOString(),
|
|
output: {
|
|
title: `${icon} ${process.env.CHECK_NAME} — ${label}`,
|
|
summary,
|
|
text: `[View workflow run](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})`
|
|
}
|
|
});
|