Implement Ansible roles for Rocky Linux Testing Framework
- Added `bootstrap_sparrowdo` role for bootstrapping Sparrowdo on a VM. - Introduced `cleanup_vm` role for cleaning up VMs and disk images. - Created `download_image` role to download and cache QCOW2 images. - Developed `golden_image` role for creating and customizing golden images. - Implemented `provision_vm` role for provisioning VMs as linked clones. - Added `run_test` role for executing tests with Sparrowdo. - Created playbooks for building golden images, running single tests, and running test suites. - Enhanced documentation with usage examples, configuration details, and troubleshooting tips. - Added support for multiple cloud providers (AWS, Azure) in the test execution workflow. Signed-off-by: Stephen Simpson <ssimpson89@users.noreply.github.com>
This commit is contained in:
194
ansible/README.md
Normal file
194
ansible/README.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# Ansible Implementation
|
||||
|
||||
This directory contains the Ansible-based implementation of the Rocky Linux Testing Framework with provider-aware infrastructure support.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
cd ansible
|
||||
|
||||
# 1. Build golden image (libvirt only)
|
||||
ansible-playbook playbooks/libvirt/build-golden-image.yml
|
||||
|
||||
# 2. Run all tests
|
||||
ansible-playbook playbooks/run-tests.yml
|
||||
|
||||
# 3. Run specific test
|
||||
ansible-playbook playbooks/run-tests.yml -e "test_filter=Sparky_Knot"
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
See [ANSIBLE-GUIDE.md](../docs/ANSIBLE-GUIDE.md) for complete documentation.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
ansible/
|
||||
├── ansible.cfg # Ansible configuration
|
||||
├── inventory/
|
||||
│ ├── hosts.yml # Provider groups (libvirt, aws, azure)
|
||||
│ └── group_vars/
|
||||
│ ├── all.yml # Provider-agnostic variables
|
||||
│ ├── libvirt.yml # Libvirt-specific settings
|
||||
│ ├── aws.yml # AWS-specific settings (placeholder)
|
||||
│ └── azure.yml # Azure-specific settings (placeholder)
|
||||
├── vars/
|
||||
│ └── test-definitions.yml # Test repository list
|
||||
├── tasks/
|
||||
│ └── run-test-with-infrastructure.yml # Provider-aware orchestration
|
||||
├── playbooks/
|
||||
│ ├── libvirt/ # Libvirt-specific playbooks
|
||||
│ │ └── build-golden-image.yml
|
||||
│ ├── aws/ # AWS-specific playbooks (future)
|
||||
│ ├── azure/ # Azure-specific playbooks (future)
|
||||
│ └── run-tests.yml # Provider-agnostic test runner
|
||||
└── roles/
|
||||
├── download_image/ # Download QCOW2 images
|
||||
├── golden_image/ # Build golden image
|
||||
├── bootstrap_sparrowdo/# Bootstrap Sparrowdo
|
||||
├── provision_vm/ # Provision VMs (libvirt)
|
||||
├── run_test/ # Execute Sparrowdo tests
|
||||
└── cleanup_vm/ # Cleanup VMs (libvirt)
|
||||
```
|
||||
|
||||
## Roles
|
||||
|
||||
| Role | Purpose |
|
||||
|------|---------|
|
||||
| `download_image` | Download and cache QCOW2 images |
|
||||
| `golden_image` | Create golden image with virt-customize |
|
||||
| `bootstrap_sparrowdo` | Bootstrap Sparrowdo on golden image |
|
||||
| `provision_vm` | Provision VM as linked clone |
|
||||
| `run_test` | Execute Sparrowdo test |
|
||||
| `cleanup_vm` | Clean up VMs and disks |
|
||||
|
||||
## Playbooks
|
||||
|
||||
### Provider-Agnostic
|
||||
| Playbook | Purpose |
|
||||
|----------|---------|
|
||||
| `run-tests.yml` | Run tests from test-definitions.yml (works on any provider) |
|
||||
|
||||
### Libvirt-Specific
|
||||
| Playbook | Purpose |
|
||||
|----------|---------|
|
||||
| `libvirt/build-golden-image.yml` | Build golden image with interactive prompts |
|
||||
|
||||
### Future
|
||||
- `aws/` - AWS-specific setup playbooks
|
||||
- `azure/` - Azure-specific setup playbooks
|
||||
|
||||
## Configuration
|
||||
|
||||
### Provider Groups
|
||||
|
||||
The inventory is organized by provider type:
|
||||
- **libvirt** - Local VM testing (requires golden image)
|
||||
- **aws** - AWS EC2 testing (uses AMIs, ready for implementation)
|
||||
- **azure** - Azure VM testing (uses VM images, ready for implementation)
|
||||
|
||||
### Test Definitions
|
||||
|
||||
Edit `vars/test-definitions.yml` to add/modify tests:
|
||||
```yaml
|
||||
tests:
|
||||
- name: "Sparky_Knot"
|
||||
repo_url: "https://git.resf.org/testing/Sparky_Knot.git"
|
||||
# Optional: branch, timeout
|
||||
```
|
||||
|
||||
### Provider-Specific Settings
|
||||
|
||||
- **all.yml** - Settings for all providers (SSH, Sparrowdo, etc.)
|
||||
- **libvirt.yml** - VM resources, image paths, packages
|
||||
- **aws.yml** - AMI IDs, instance types (when implemented)
|
||||
- **azure.yml** - Image references, VM sizes (when implemented)
|
||||
|
||||
## Examples
|
||||
|
||||
### Build Golden Image (Libvirt Only)
|
||||
|
||||
```bash
|
||||
# Interactive prompt for Rocky image URL
|
||||
ansible-playbook playbooks/libvirt/build-golden-image.yml
|
||||
```
|
||||
|
||||
### Run All Tests
|
||||
|
||||
```bash
|
||||
ansible-playbook playbooks/run-tests.yml
|
||||
```
|
||||
|
||||
### Run Specific Test
|
||||
|
||||
```bash
|
||||
ansible-playbook playbooks/run-tests.yml -e "test_filter=Sparky_Knot"
|
||||
```
|
||||
|
||||
### Run Tests on Specific Provider
|
||||
|
||||
```bash
|
||||
ansible-playbook playbooks/run-tests.yml --limit libvirt
|
||||
# Future: --limit aws or --limit azure
|
||||
```
|
||||
|
||||
### Override VM Resources
|
||||
|
||||
```bash
|
||||
ansible-playbook playbooks/run-tests.yml -e "vm_memory=4096" -e "vm_vcpus=4"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Verbose Output
|
||||
|
||||
```bash
|
||||
ansible-playbook playbooks/run-tests.yml -vvv
|
||||
```
|
||||
|
||||
### Check Syntax
|
||||
|
||||
```bash
|
||||
ansible-playbook playbooks/libvirt/build-golden-image.yml --syntax-check
|
||||
```
|
||||
|
||||
### List Tasks
|
||||
|
||||
```bash
|
||||
ansible-playbook playbooks/run-tests.yml --list-tasks
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Provider-aware** - Supports multiple infrastructure providers (libvirt, AWS, Azure)
|
||||
- **Test definitions** - Centralized test repository list in `vars/test-definitions.yml`
|
||||
- **Interactive prompts** - No need to remember image URLs or flags
|
||||
- **Result tracking** - Shows passed/failed tests with summary report
|
||||
- **Automatic cleanup** - Infrastructure cleaned up after each test
|
||||
- **Idempotent** - Safe to re-run
|
||||
- **Integration with Ascender** - Works with Ansible automation platform
|
||||
|
||||
### Script Mapping
|
||||
|
||||
| Shell Script | Ansible Role |
|
||||
|--------------|--------------|
|
||||
| `download-image.sh` | `download_image` |
|
||||
| `setup_base.sh` + `build-golden.sh` | `golden_image` |
|
||||
| `bootstrap_golden.sh` | `bootstrap_sparrowdo` |
|
||||
| `provision_vm.sh` | `provision_vm` |
|
||||
| `run-test.sh` | `run_test` |
|
||||
| `cleanup_vm.sh` | `cleanup_vm` |
|
||||
|
||||
## Prerequisites
|
||||
|
||||
```bash
|
||||
sudo dnf install -y ansible qemu-kvm libvirt virt-install guestfs-tools rakudo
|
||||
sudo systemctl enable --now libvirtd
|
||||
sudo usermod -a -G libvirt $(whoami)
|
||||
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -N ""
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
[Your License Here]
|
||||
20
ansible/ansible.cfg
Normal file
20
ansible/ansible.cfg
Normal file
@@ -0,0 +1,20 @@
|
||||
[defaults]
|
||||
inventory = inventory/hosts.yml
|
||||
roles_path = roles
|
||||
host_key_checking = False
|
||||
retry_files_enabled = False
|
||||
gathering = smart
|
||||
fact_caching = jsonfile
|
||||
fact_caching_connection = /tmp/ansible_facts
|
||||
fact_caching_timeout = 3600
|
||||
stdout_callback = default
|
||||
callbacks_enabled = timer
|
||||
|
||||
[privilege_escalation]
|
||||
become_method = sudo
|
||||
become_user = root
|
||||
become_ask_pass = False
|
||||
|
||||
[ssh_connection]
|
||||
pipelining = True
|
||||
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
|
||||
13
ansible/inventory/group_vars/all.yml
Normal file
13
ansible/inventory/group_vars/all.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
# Global configuration
|
||||
|
||||
# SSH defaults
|
||||
ssh_user: "rocky"
|
||||
|
||||
# Default image password
|
||||
default_image_password: "rocky"
|
||||
|
||||
# Rocky Linux image URLs
|
||||
rocky9_qcow2_url: "https://download.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2"
|
||||
rocky8_qcow2_url: "https://download.rockylinux.org/pub/rocky/8/images/x86_64/Rocky-8-GenericCloud-Base.latest.x86_64.qcow2"
|
||||
qcow2_url: "{{ rocky9_qcow2_url }}"
|
||||
29
ansible/inventory/group_vars/aws.yml
Normal file
29
ansible/inventory/group_vars/aws.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
# AWS provider-specific configuration
|
||||
# TODO: Implement AWS-specific settings
|
||||
|
||||
# Provider identification
|
||||
provider: aws
|
||||
|
||||
# AWS-specific variables (to be implemented)
|
||||
# aws_region: "us-east-1"
|
||||
# aws_instance_type: "t3.medium"
|
||||
# aws_vpc_id: ""
|
||||
# aws_subnet_id: ""
|
||||
# aws_security_group_id: ""
|
||||
|
||||
# Rocky Linux AMI IDs (to be populated)
|
||||
# rocky9_ami: "ami-xxxxxxxxx" # Rocky 9 AMI for your region
|
||||
# rocky8_ami: "ami-xxxxxxxxx" # Rocky 8 AMI for your region
|
||||
|
||||
# Default AMI (override with -e "ami_id=ami-xxx")
|
||||
# ami_id: "{{ rocky9_ami }}"
|
||||
|
||||
# Instance settings
|
||||
# instance_name_prefix: "test"
|
||||
# instance_tags:
|
||||
# Environment: "testing"
|
||||
# ManagedBy: "ansible"
|
||||
|
||||
# Note: AWS does not require golden image creation
|
||||
# Workflow: Select AMI -> Launch instance -> Run Sparrowdo tests -> Terminate
|
||||
36
ansible/inventory/group_vars/azure.yml
Normal file
36
ansible/inventory/group_vars/azure.yml
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
# Azure provider-specific configuration
|
||||
# TODO: Implement Azure-specific settings
|
||||
|
||||
# Provider identification
|
||||
provider: azure
|
||||
|
||||
# Azure-specific variables (to be implemented)
|
||||
# azure_location: "eastus"
|
||||
# azure_resource_group: ""
|
||||
# azure_vm_size: "Standard_B2s"
|
||||
# azure_virtual_network: ""
|
||||
# azure_subnet: ""
|
||||
# azure_security_group: ""
|
||||
|
||||
# Rocky Linux image references (to be populated)
|
||||
# rocky9_image:
|
||||
# publisher: "resf"
|
||||
# offer: "rockylinux-9"
|
||||
# sku: "rockylinux-9"
|
||||
# version: "latest"
|
||||
|
||||
# rocky8_image:
|
||||
# publisher: "resf"
|
||||
# offer: "rockylinux-8"
|
||||
# sku: "rockylinux-8"
|
||||
# version: "latest"
|
||||
|
||||
# VM settings
|
||||
# vm_name_prefix: "test"
|
||||
# vm_tags:
|
||||
# Environment: "testing"
|
||||
# ManagedBy: "ansible"
|
||||
|
||||
# Note: Azure does not require golden image creation
|
||||
# Workflow: Select image -> Create VM -> Run Sparrowdo tests -> Delete VM
|
||||
21
ansible/inventory/group_vars/libvirt.yml
Normal file
21
ansible/inventory/group_vars/libvirt.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
# Libvirt provider configuration
|
||||
|
||||
provider: libvirt
|
||||
|
||||
# Image directories
|
||||
images_dir: "/var/lib/libvirt/images"
|
||||
base_images_dir: "{{ images_dir }}/base"
|
||||
golden_images_dir: "{{ images_dir }}/golden"
|
||||
golden_image_path: "{{ golden_images_dir }}/rocky-golden.qcow2"
|
||||
|
||||
# Download settings
|
||||
force_download: false
|
||||
download_timeout: 1800
|
||||
|
||||
# Golden image settings
|
||||
root_password: "{{ default_image_password }}"
|
||||
|
||||
# VM settings
|
||||
vm_memory: 4096
|
||||
vm_vcpus: 4
|
||||
16
ansible/inventory/hosts.yml
Normal file
16
ansible/inventory/hosts.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
all:
|
||||
children:
|
||||
|
||||
libvirt:
|
||||
hosts:
|
||||
ssimpson-pc:
|
||||
ansible_host: 172.16.1.141
|
||||
ansible_user: jenkins
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
|
||||
aws:
|
||||
hosts: {}
|
||||
|
||||
azure:
|
||||
hosts: {}
|
||||
19
ansible/playbooks/aws/README.md
Normal file
19
ansible/playbooks/aws/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# AWS Playbooks
|
||||
|
||||
This directory will contain AWS-specific playbooks for the Rocky Linux Testing Framework.
|
||||
|
||||
## Future Playbooks
|
||||
|
||||
- `setup.yml` - Configure AWS resources (VPC, security groups, etc.)
|
||||
- `create-ami.yml` - Build custom Rocky Linux AMIs
|
||||
- `cleanup.yml` - Clean up AWS resources
|
||||
|
||||
## Workflow
|
||||
|
||||
AWS testing doesn't require golden image creation. Instead:
|
||||
1. Select Rocky Linux AMI
|
||||
2. Launch EC2 instance
|
||||
3. Run Sparrowdo tests
|
||||
4. Terminate instance
|
||||
|
||||
All tests use the main `playbooks/run-tests.yml` playbook, which automatically handles AWS-specific infrastructure provisioning.
|
||||
19
ansible/playbooks/azure/README.md
Normal file
19
ansible/playbooks/azure/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Azure Playbooks
|
||||
|
||||
This directory will contain Azure-specific playbooks for the Rocky Linux Testing Framework.
|
||||
|
||||
## Future Playbooks
|
||||
|
||||
- `setup.yml` - Configure Azure resources (resource groups, networks, etc.)
|
||||
- `create-image.yml` - Build custom Rocky Linux VM images
|
||||
- `cleanup.yml` - Clean up Azure resources
|
||||
|
||||
## Workflow
|
||||
|
||||
Azure testing doesn't require golden image creation. Instead:
|
||||
1. Select Rocky Linux VM image
|
||||
2. Create Azure VM
|
||||
3. Run Sparrowdo tests
|
||||
4. Delete VM
|
||||
|
||||
All tests use the main `playbooks/run-tests.yml` playbook, which automatically handles Azure-specific infrastructure provisioning.
|
||||
78
ansible/playbooks/libvirt/build-golden-image.yml
Normal file
78
ansible/playbooks/libvirt/build-golden-image.yml
Normal file
@@ -0,0 +1,78 @@
|
||||
---
|
||||
# Build a golden image: Download -> Customize -> Bootstrap Sparrowdo
|
||||
|
||||
- name: Build Golden Image
|
||||
hosts: libvirt
|
||||
gather_facts: true
|
||||
|
||||
vars_prompt:
|
||||
- name: qcow2_url
|
||||
prompt: "Rocky Linux QCOW2 URL (Enter for Rocky 9 default)"
|
||||
default: "https://download.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2"
|
||||
private: false
|
||||
|
||||
pre_tasks:
|
||||
- name: Verify sparrowdo is installed
|
||||
stat:
|
||||
path: ~/.raku/bin/sparrowdo
|
||||
register: sparrowdo_check
|
||||
failed_when: not sparrowdo_check.stat.exists
|
||||
|
||||
- name: Set build paths
|
||||
set_fact:
|
||||
build_id: "{{ lookup('pipe', 'date +%s') }}"
|
||||
|
||||
- name: Set golden image path
|
||||
set_fact:
|
||||
timestamped_golden_path: "{{ golden_images_dir }}/rocky-golden-{{ build_id }}.qcow2"
|
||||
|
||||
- name: Ensure directories exist
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
become: true
|
||||
loop:
|
||||
- "{{ golden_images_dir }}"
|
||||
- /tmp/rocky-test-keys
|
||||
|
||||
- name: Generate SSH keys if needed
|
||||
command: ssh-keygen -t rsa -b 4096 -f /tmp/rocky-test-keys/id_rsa -N "" -C "rocky-test"
|
||||
args:
|
||||
creates: /tmp/rocky-test-keys/id_rsa
|
||||
|
||||
- name: Set SSH key paths
|
||||
set_fact:
|
||||
ssh_private_key_path: /tmp/rocky-test-keys/id_rsa
|
||||
ssh_public_key_path: /tmp/rocky-test-keys/id_rsa.pub
|
||||
|
||||
tasks:
|
||||
- name: Download base image
|
||||
include_role:
|
||||
name: download_image
|
||||
vars:
|
||||
image_path_var: "base_image_path"
|
||||
|
||||
- name: Create golden image
|
||||
include_role:
|
||||
name: golden_image
|
||||
vars:
|
||||
golden_image_path: "{{ timestamped_golden_path }}"
|
||||
|
||||
- name: Bootstrap Sparrowdo
|
||||
include_role:
|
||||
name: bootstrap_sparrowdo
|
||||
vars:
|
||||
golden_image_path: "{{ timestamped_golden_path }}"
|
||||
|
||||
- name: Create symlink to latest golden image
|
||||
file:
|
||||
src: "{{ timestamped_golden_path }}"
|
||||
dest: "{{ golden_image_path }}"
|
||||
state: link
|
||||
force: yes
|
||||
become: true
|
||||
|
||||
- name: Done
|
||||
debug:
|
||||
msg: "Golden image ready: {{ golden_image_path }}"
|
||||
123
ansible/playbooks/run-tests.yml
Normal file
123
ansible/playbooks/run-tests.yml
Normal file
@@ -0,0 +1,123 @@
|
||||
---
|
||||
# Provider-aware test suite runner
|
||||
# Loads tests from vars/test-definitions.yml
|
||||
# Handles infrastructure provisioning/cleanup based on provider type
|
||||
|
||||
- name: Run Test Suite
|
||||
hosts: all
|
||||
gather_facts: true
|
||||
|
||||
vars_files:
|
||||
- ../vars/test-definitions.yml
|
||||
|
||||
vars:
|
||||
# Test filtering (override with -e "test_filter=ssh")
|
||||
test_filter: "{{ default_test_filter }}"
|
||||
|
||||
# Results tracking
|
||||
test_results: []
|
||||
passed_tests: []
|
||||
failed_tests: []
|
||||
|
||||
pre_tasks:
|
||||
- name: Filter tests based on test_filter
|
||||
set_fact:
|
||||
filtered_tests: "{{ tests | selectattr('name', 'match', test_filter) | list }}"
|
||||
|
||||
- name: Validate test list
|
||||
fail:
|
||||
msg: "No tests match filter: {{ test_filter }}"
|
||||
when: filtered_tests | length == 0
|
||||
|
||||
- name: Display test suite information
|
||||
debug:
|
||||
msg:
|
||||
- "=========================================="
|
||||
- "Running Test Suite"
|
||||
- "=========================================="
|
||||
- "Provider: {{ provider }}"
|
||||
- "Total tests: {{ filtered_tests | length }}"
|
||||
- "Test filter: {{ test_filter }}"
|
||||
- "=========================================="
|
||||
|
||||
- name: Display tests to run
|
||||
debug:
|
||||
msg: " {{ loop_index + 1 }}. {{ item.name }}"
|
||||
loop: "{{ filtered_tests }}"
|
||||
loop_control:
|
||||
index_var: loop_index
|
||||
|
||||
tasks:
|
||||
# Provider-aware test execution loop
|
||||
- name: Execute test suite
|
||||
block:
|
||||
- name: Run each test with provider-specific infrastructure
|
||||
include_tasks: ../tasks/run-test-with-infrastructure.yml
|
||||
loop: "{{ filtered_tests }}"
|
||||
loop_control:
|
||||
loop_var: test_item
|
||||
label: "{{ test_item.name }}"
|
||||
rescue:
|
||||
- name: Handle catastrophic failure
|
||||
debug:
|
||||
msg: "Test suite encountered a critical error"
|
||||
|
||||
post_tasks:
|
||||
- name: Collect test results
|
||||
set_fact:
|
||||
passed_tests: "{{ test_results | selectattr('status', 'equalto', 'passed') | list }}"
|
||||
failed_tests: "{{ test_results | selectattr('status', 'equalto', 'failed') | list }}"
|
||||
|
||||
- name: Display test results summary
|
||||
debug:
|
||||
msg:
|
||||
- "=========================================="
|
||||
- "Test Suite Results"
|
||||
- "=========================================="
|
||||
- "Provider: {{ provider }}"
|
||||
- "Total Tests: {{ test_results | length }}"
|
||||
- "Passed: {{ passed_tests | length }}"
|
||||
- "Failed: {{ failed_tests | length }}"
|
||||
- "=========================================="
|
||||
|
||||
- name: Display passed tests
|
||||
debug:
|
||||
msg: " ✓ {{ item.name }}"
|
||||
loop: "{{ passed_tests }}"
|
||||
when: passed_tests | length > 0
|
||||
|
||||
- name: Display failed tests
|
||||
debug:
|
||||
msg: " ✗ {{ item.name }}: {{ item.error | default('Unknown error') }}"
|
||||
loop: "{{ failed_tests }}"
|
||||
when: failed_tests | length > 0
|
||||
|
||||
- name: Save results to file
|
||||
copy:
|
||||
content: |
|
||||
Test Suite Results - {{ ansible_date_time.iso8601 }}
|
||||
Provider: {{ provider }}
|
||||
|
||||
Summary:
|
||||
- Total: {{ test_results | length }}
|
||||
- Passed: {{ passed_tests | length }}
|
||||
- Failed: {{ failed_tests | length }}
|
||||
|
||||
Passed Tests:
|
||||
{% for test in passed_tests %}
|
||||
- {{ test.name }}
|
||||
{% endfor %}
|
||||
|
||||
Failed Tests:
|
||||
{% for test in failed_tests %}
|
||||
- {{ test.name }}: {{ test.error | default('Unknown error') }}
|
||||
{% endfor %}
|
||||
dest: "{{ logs_dir }}/test-results-{{ ansible_date_time.epoch }}.txt"
|
||||
when: test_results | length > 0
|
||||
|
||||
- name: Fail if any tests failed
|
||||
fail:
|
||||
msg: "{{ failed_tests | length }} test(s) failed"
|
||||
when:
|
||||
- failed_tests | length > 0
|
||||
- not continue_on_failure
|
||||
9
ansible/roles/bootstrap_sparrowdo/defaults/main.yml
Normal file
9
ansible/roles/bootstrap_sparrowdo/defaults/main.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
# Timeouts (seconds)
|
||||
vm_boot_timeout: 60
|
||||
ssh_port_timeout: 300
|
||||
bootstrap_timeout: 900
|
||||
|
||||
# VM resources
|
||||
vm_memory: 2048
|
||||
vm_vcpus: 2
|
||||
100
ansible/roles/bootstrap_sparrowdo/tasks/main.yml
Normal file
100
ansible/roles/bootstrap_sparrowdo/tasks/main.yml
Normal file
@@ -0,0 +1,100 @@
|
||||
---
|
||||
- name: Verify golden image exists
|
||||
stat:
|
||||
path: "{{ golden_image_path }}"
|
||||
register: golden_stat
|
||||
failed_when: not golden_stat.stat.exists
|
||||
|
||||
- name: Set bootstrap VM name
|
||||
set_fact:
|
||||
bootstrap_vm: "bootstrap-{{ ansible_date_time.epoch }}"
|
||||
|
||||
- name: Bootstrap Sparrowdo
|
||||
block:
|
||||
- name: Create overlay disk
|
||||
command: >
|
||||
qemu-img create -f qcow2
|
||||
-b {{ golden_image_path }} -F qcow2
|
||||
/var/lib/libvirt/images/{{ bootstrap_vm }}.qcow2
|
||||
become: true
|
||||
|
||||
- name: Start bootstrap VM
|
||||
command: >
|
||||
virt-install
|
||||
--name {{ bootstrap_vm }}
|
||||
--memory {{ vm_memory }}
|
||||
--vcpus {{ vm_vcpus }}
|
||||
--disk path=/var/lib/libvirt/images/{{ bootstrap_vm }}.qcow2,format=qcow2
|
||||
--import
|
||||
--os-variant rocky9-unknown
|
||||
--network network=default
|
||||
--noautoconsole
|
||||
--wait 0
|
||||
become: true
|
||||
|
||||
- name: Wait for VM IP
|
||||
shell: >
|
||||
virsh -c qemu:///system domifaddr {{ bootstrap_vm }} --source lease 2>/dev/null |
|
||||
awk '/ipv4/ {print $4}' | cut -d/ -f1 | head -1
|
||||
become: true
|
||||
register: vm_ip
|
||||
until: vm_ip.stdout != "" and vm_ip.stdout != "0.0.0.0"
|
||||
retries: "{{ vm_boot_timeout }}"
|
||||
delay: 2
|
||||
changed_when: false
|
||||
|
||||
- name: Wait for SSH
|
||||
wait_for:
|
||||
host: "{{ vm_ip.stdout }}"
|
||||
port: 22
|
||||
timeout: "{{ ssh_port_timeout }}"
|
||||
|
||||
- name: Run Sparrowdo bootstrap
|
||||
command:
|
||||
argv:
|
||||
- "~/.raku/bin/sparrowdo"
|
||||
- "--host={{ vm_ip.stdout }}"
|
||||
- "--ssh_user={{ ssh_user }}"
|
||||
- "--ssh_private_key={{ ssh_private_key_path }}"
|
||||
- "--bootstrap"
|
||||
timeout: "{{ bootstrap_timeout }}"
|
||||
register: bootstrap_result
|
||||
retries: 3
|
||||
delay: 5
|
||||
until: bootstrap_result.rc == 0
|
||||
|
||||
- name: Shutdown VM
|
||||
command: >
|
||||
ssh -i {{ ssh_private_key_path }}
|
||||
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
|
||||
{{ ssh_user }}@{{ vm_ip.stdout }} 'sudo shutdown -h now'
|
||||
ignore_errors: true
|
||||
|
||||
- name: Wait for shutdown
|
||||
shell: virsh -c qemu:///system list --name | grep -q "{{ bootstrap_vm }}"
|
||||
become: true
|
||||
register: vm_running
|
||||
until: vm_running.rc != 0
|
||||
retries: 30
|
||||
delay: 2
|
||||
failed_when: false
|
||||
|
||||
always:
|
||||
- name: Force stop VM if running
|
||||
command: "virsh -c qemu:///system destroy {{ bootstrap_vm }}"
|
||||
become: true
|
||||
ignore_errors: true
|
||||
changed_when: false
|
||||
|
||||
- name: Undefine VM
|
||||
command: "virsh -c qemu:///system undefine {{ bootstrap_vm }}"
|
||||
become: true
|
||||
ignore_errors: true
|
||||
changed_when: false
|
||||
|
||||
- name: Remove overlay disk
|
||||
file:
|
||||
path: "/var/lib/libvirt/images/{{ bootstrap_vm }}.qcow2"
|
||||
state: absent
|
||||
become: true
|
||||
ignore_errors: true
|
||||
17
ansible/roles/cleanup_vm/defaults/main.yml
Normal file
17
ansible/roles/cleanup_vm/defaults/main.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
# Default variables for cleanup_vm role
|
||||
|
||||
# VM name to cleanup (required)
|
||||
vm_name: ""
|
||||
|
||||
# Force destroy even if running
|
||||
force_destroy: true
|
||||
|
||||
# Remove disk image
|
||||
remove_disk: true
|
||||
|
||||
# Cleanup multiple VMs matching pattern
|
||||
cleanup_pattern: ""
|
||||
|
||||
# Cleanup all VMs in list
|
||||
cleanup_vm_list: []
|
||||
23
ansible/roles/cleanup_vm/tasks/cleanup_single.yml
Normal file
23
ansible/roles/cleanup_vm/tasks/cleanup_single.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
# Cleanup a single VM (used in loop)
|
||||
|
||||
- name: Destroy VM {{ vm_to_cleanup }}
|
||||
command: virsh -c qemu:///system destroy {{ vm_to_cleanup }}
|
||||
become: true
|
||||
register: destroy_result
|
||||
failed_when: false
|
||||
changed_when: destroy_result.rc == 0
|
||||
|
||||
- name: Undefine VM {{ vm_to_cleanup }}
|
||||
command: virsh -c qemu:///system undefine {{ vm_to_cleanup }}
|
||||
become: true
|
||||
register: undefine_result
|
||||
failed_when: false
|
||||
changed_when: undefine_result.rc == 0
|
||||
|
||||
- name: Remove disk for VM {{ vm_to_cleanup }}
|
||||
file:
|
||||
path: "/var/lib/libvirt/images/{{ vm_to_cleanup }}.qcow2"
|
||||
state: absent
|
||||
become: true
|
||||
when: remove_disk
|
||||
77
ansible/roles/cleanup_vm/tasks/main.yml
Normal file
77
ansible/roles/cleanup_vm/tasks/main.yml
Normal file
@@ -0,0 +1,77 @@
|
||||
---
|
||||
# Tasks for cleanup_vm role
|
||||
|
||||
- name: Cleanup single VM
|
||||
when: vm_name != ""
|
||||
block:
|
||||
- name: Display cleanup info for single VM
|
||||
debug:
|
||||
msg: "Starting cleanup for VM: {{ vm_name }}"
|
||||
|
||||
- name: Destroy VM
|
||||
command: virsh -c qemu:///system destroy {{ vm_name }}
|
||||
become: true
|
||||
register: destroy_result
|
||||
failed_when: false
|
||||
changed_when: destroy_result.rc == 0
|
||||
|
||||
- name: Display destroy result
|
||||
debug:
|
||||
msg: "{{ 'VM was not running' if destroy_result.rc != 0 else 'VM destroyed' }}"
|
||||
|
||||
- name: Undefine VM
|
||||
command: virsh -c qemu:///system undefine {{ vm_name }}
|
||||
become: true
|
||||
register: undefine_result
|
||||
failed_when: false
|
||||
changed_when: undefine_result.rc == 0
|
||||
|
||||
- name: Display undefine result
|
||||
debug:
|
||||
msg: "{{ 'VM definition already removed' if undefine_result.rc != 0 else 'VM undefined' }}"
|
||||
|
||||
- name: Remove disk image
|
||||
file:
|
||||
path: "/var/lib/libvirt/images/{{ vm_name }}.qcow2"
|
||||
state: absent
|
||||
become: true
|
||||
when: remove_disk
|
||||
|
||||
- name: Display cleanup completion
|
||||
debug:
|
||||
msg: "Cleanup complete for {{ vm_name }}"
|
||||
|
||||
- name: Cleanup VMs matching pattern
|
||||
when: cleanup_pattern != ""
|
||||
block:
|
||||
- name: Get list of VMs matching pattern
|
||||
shell: virsh -c qemu:///system list --all --name | grep -E "{{ cleanup_pattern }}"
|
||||
become: true
|
||||
register: matching_vms
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: Display matching VMs
|
||||
debug:
|
||||
msg: "Found {{ matching_vms.stdout_lines | length }} VMs matching pattern: {{ cleanup_pattern }}"
|
||||
when: matching_vms.stdout_lines | length > 0
|
||||
|
||||
- name: Cleanup each matching VM
|
||||
include_tasks: cleanup_single.yml
|
||||
loop: "{{ matching_vms.stdout_lines }}"
|
||||
loop_control:
|
||||
loop_var: vm_to_cleanup
|
||||
when: matching_vms.stdout_lines | length > 0
|
||||
|
||||
- name: Cleanup VMs from list
|
||||
when: cleanup_vm_list | length > 0
|
||||
block:
|
||||
- name: Display VMs to cleanup
|
||||
debug:
|
||||
msg: "Cleaning up {{ cleanup_vm_list | length }} VMs from list"
|
||||
|
||||
- name: Cleanup each VM in list
|
||||
include_tasks: cleanup_single.yml
|
||||
loop: "{{ cleanup_vm_list }}"
|
||||
loop_control:
|
||||
loop_var: vm_to_cleanup
|
||||
6
ansible/roles/download_image/defaults/main.yml
Normal file
6
ansible/roles/download_image/defaults/main.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
# Default variables for download_image role
|
||||
# Most settings come from inventory (group_vars)
|
||||
|
||||
# Return variable name for the downloaded image path
|
||||
image_path_var: "base_image_path"
|
||||
24
ansible/roles/download_image/tasks/main.yml
Normal file
24
ansible/roles/download_image/tasks/main.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
- name: Ensure base images directory exists
|
||||
file:
|
||||
path: "{{ base_images_dir }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
become: true
|
||||
|
||||
- name: Set cached image path
|
||||
set_fact:
|
||||
cached_image_path: "{{ base_images_dir }}/{{ qcow2_url | basename }}"
|
||||
|
||||
- name: Download QCOW2 image
|
||||
get_url:
|
||||
url: "{{ qcow2_url }}"
|
||||
dest: "{{ cached_image_path }}"
|
||||
mode: '0644'
|
||||
timeout: "{{ download_timeout }}"
|
||||
force: "{{ force_download }}"
|
||||
become: true
|
||||
|
||||
- name: Set image path fact
|
||||
set_fact:
|
||||
"{{ image_path_var }}": "{{ cached_image_path }}"
|
||||
4
ansible/roles/golden_image/defaults/main.yml
Normal file
4
ansible/roles/golden_image/defaults/main.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
# Paths (passed from playbook)
|
||||
golden_image_base_image_path: ""
|
||||
golden_image_path: ""
|
||||
17
ansible/roles/golden_image/tasks/customize.sh
Normal file
17
ansible/roles/golden_image/tasks/customize.sh
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
set -eux
|
||||
|
||||
# Create user if it doesn't exist
|
||||
if ! id -u rocky >/dev/null 2>&1; then
|
||||
useradd -m rocky
|
||||
fi
|
||||
|
||||
# Set password
|
||||
echo "rocky:rockypass" | chpasswd
|
||||
|
||||
# Sudoers
|
||||
echo "rocky ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/rocky
|
||||
chmod 0440 /etc/sudoers.d/rocky
|
||||
|
||||
# Enable ssh
|
||||
systemctl enable sshd
|
||||
35
ansible/roles/golden_image/tasks/main.yml
Normal file
35
ansible/roles/golden_image/tasks/main.yml
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
- name: Verify base image exists
|
||||
ansible.builtin.stat:
|
||||
path: "{{ golden_image_base_image_path }}"
|
||||
register: golden_image_base_image_stat
|
||||
failed_when: not golden_image_base_image_stat.stat.exists
|
||||
|
||||
- name: Ensure golden image directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ golden_image_path | dirname }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
become: true
|
||||
|
||||
- name: Copy base image to golden image
|
||||
ansible.builtin.copy:
|
||||
src: "{{ golden_image_base_image_path }}"
|
||||
dest: "{{ golden_image_path }}"
|
||||
remote_src: true
|
||||
mode: '0644'
|
||||
become: true
|
||||
|
||||
- name: Customize golden image
|
||||
ansible.builtin.command: >
|
||||
virt-customize -a {{ golden_image_path }}
|
||||
--install perl,git,wget,tar,openssh-server,vim
|
||||
--run {{ role_path }}/tasks/customize.sh
|
||||
--ssh-inject root:file:{{ ssh_public_key_path }}
|
||||
--ssh-inject rocky:file:{{ ssh_public_key_path }}
|
||||
--root-password password:{{ root_password }}
|
||||
--selinux-relabel
|
||||
changed_when: false
|
||||
environment:
|
||||
LIBGUESTFS_BACKEND: direct
|
||||
become: true
|
||||
18
ansible/roles/provision_vm/defaults/main.yml
Normal file
18
ansible/roles/provision_vm/defaults/main.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
# Default variables for provision_vm role
|
||||
# Most settings come from inventory (group_vars/libvirt.yml)
|
||||
|
||||
# VM name (required, passed from playbook/tasks)
|
||||
vm_name: ""
|
||||
|
||||
# Maximum wait time for IP (seconds)
|
||||
max_wait_ip: 30
|
||||
|
||||
# OS variant for virt-install
|
||||
os_variant: "rocky9-unknown"
|
||||
|
||||
# Use transient VM (doesn't survive reboot)
|
||||
vm_transient: true
|
||||
|
||||
# Return variable name for VM IP
|
||||
vm_ip_var: "provisioned_vm_ip"
|
||||
82
ansible/roles/provision_vm/tasks/main.yml
Normal file
82
ansible/roles/provision_vm/tasks/main.yml
Normal file
@@ -0,0 +1,82 @@
|
||||
---
|
||||
# Tasks for provision_vm role
|
||||
|
||||
- name: Validate VM name
|
||||
fail:
|
||||
msg: "vm_name is required"
|
||||
when: vm_name == ""
|
||||
|
||||
- name: Validate golden image path
|
||||
stat:
|
||||
path: "{{ golden_image_path }}"
|
||||
register: golden_image_stat
|
||||
failed_when: not golden_image_stat.stat.exists
|
||||
|
||||
- name: Set VM disk path
|
||||
set_fact:
|
||||
vm_disk_path: "/var/lib/libvirt/images/{{ vm_name }}.qcow2"
|
||||
|
||||
- name: Display provisioning info
|
||||
debug:
|
||||
msg: "Creating VM: {{ vm_name }}"
|
||||
|
||||
- name: Create linked clone overlay disk
|
||||
command: >
|
||||
qemu-img create -f qcow2
|
||||
-b {{ golden_image_path }}
|
||||
-F qcow2
|
||||
{{ vm_disk_path }}
|
||||
become: true
|
||||
register: disk_created
|
||||
changed_when: true
|
||||
|
||||
- name: Build virt-install command
|
||||
set_fact:
|
||||
virt_install_cmd: >
|
||||
virt-install
|
||||
--name {{ vm_name }}
|
||||
--memory {{ vm_memory }}
|
||||
--vcpus {{ vm_vcpus }}
|
||||
--disk path={{ vm_disk_path }},format=qcow2
|
||||
--import
|
||||
--os-variant {{ os_variant }}
|
||||
--network network={{ vm_network }}
|
||||
--noautoconsole
|
||||
--wait 0
|
||||
{% if vm_transient %}--transient{% endif %}
|
||||
|
||||
- name: Start VM with virt-install
|
||||
command: "{{ virt_install_cmd }}"
|
||||
become: true
|
||||
register: vm_started
|
||||
changed_when: true
|
||||
failed_when: vm_started.rc != 0
|
||||
|
||||
- name: Wait for VM to obtain IP address
|
||||
shell: >
|
||||
virsh -c qemu:///system domifaddr {{ vm_name }} --source lease 2>/dev/null |
|
||||
awk '/ipv4/ {print $4}' | cut -d/ -f1 | head -1
|
||||
become: true
|
||||
register: vm_ip_result
|
||||
until: vm_ip_result.stdout != "" and vm_ip_result.stdout != "0.0.0.0"
|
||||
retries: "{{ max_wait_ip }}"
|
||||
delay: 2
|
||||
changed_when: false
|
||||
|
||||
- name: Set VM IP fact
|
||||
set_fact:
|
||||
"{{ vm_ip_var }}": "{{ vm_ip_result.stdout }}"
|
||||
|
||||
- name: Display VM IP
|
||||
debug:
|
||||
msg: "IP obtained: {{ vm_ip_result.stdout }}"
|
||||
|
||||
- name: Export VM IP and name
|
||||
set_stats:
|
||||
data:
|
||||
"{{ vm_ip_var }}": "{{ vm_ip_result.stdout }}"
|
||||
provisioned_vm_name: "{{ vm_name }}"
|
||||
|
||||
- name: Register VM for cleanup
|
||||
set_fact:
|
||||
provisioned_vms: "{{ provisioned_vms | default([]) + [{'name': vm_name, 'ip': vm_ip_result.stdout, 'disk': vm_disk_path}] }}"
|
||||
15
ansible/roles/run_test/defaults/main.yml
Normal file
15
ansible/roles/run_test/defaults/main.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
# Default variables for run_test role
|
||||
# Most settings come from inventory (group_vars)
|
||||
|
||||
# Test name (required, passed from playbook/tasks)
|
||||
test_name: ""
|
||||
|
||||
# Test repository URL (required, passed from playbook/tasks)
|
||||
test_repo_url: ""
|
||||
|
||||
# Test repository branch (passed from playbook/tasks)
|
||||
test_repo_branch: "main"
|
||||
|
||||
# Test timeout (passed from playbook/tasks)
|
||||
test_timeout: 900
|
||||
164
ansible/roles/run_test/tasks/main.yml
Normal file
164
ansible/roles/run_test/tasks/main.yml
Normal file
@@ -0,0 +1,164 @@
|
||||
---
|
||||
# Tasks for run_test role
|
||||
|
||||
- name: Validate test parameters
|
||||
fail:
|
||||
msg: "{{ item.msg }}"
|
||||
when: item.condition
|
||||
loop:
|
||||
- { condition: "{{ test_name == '' }}", msg: "test_name is required" }
|
||||
- { condition: "{{ test_repo_url == '' }}", msg: "test_repo_url is required" }
|
||||
|
||||
- name: Generate unique VM name
|
||||
set_fact:
|
||||
test_vm_name: "{{ test_name }}-{{ ansible_date_time.epoch }}"
|
||||
|
||||
- name: Create working directory
|
||||
file:
|
||||
path: "{{ work_dir }}/{{ test_vm_name }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Display test info
|
||||
debug:
|
||||
msg:
|
||||
- "Running test: {{ test_name }}"
|
||||
- "Repository: {{ test_repo_url }}"
|
||||
- "Branch: {{ test_repo_branch }}"
|
||||
- "VM: {{ test_vm_name }}"
|
||||
|
||||
# Provision VM
|
||||
- name: Provision test VM
|
||||
include_role:
|
||||
name: provision_vm
|
||||
vars:
|
||||
vm_name: "{{ test_vm_name }}"
|
||||
vm_ip_var: "test_vm_ip"
|
||||
|
||||
- name: Set VM IP variable
|
||||
set_fact:
|
||||
vm_ip: "{{ test_vm_ip }}"
|
||||
|
||||
- name: Display VM information
|
||||
debug:
|
||||
msg: "VM ready at {{ vm_ip }}"
|
||||
|
||||
# Wait for SSH
|
||||
- name: Wait for SSH to be ready
|
||||
wait_for:
|
||||
host: "{{ vm_ip }}"
|
||||
port: 22
|
||||
timeout: 60
|
||||
state: started
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Test SSH connection
|
||||
command: >
|
||||
ssh -i {{ ssh_private_key_path }}
|
||||
-o StrictHostKeyChecking=no
|
||||
-o ConnectTimeout=5
|
||||
-o UserKnownHostsFile=/dev/null
|
||||
{{ ssh_user }}@{{ vm_ip }}
|
||||
'echo SSH ready'
|
||||
register: ssh_test
|
||||
until: ssh_test.rc == 0
|
||||
retries: 30
|
||||
delay: 2
|
||||
changed_when: false
|
||||
|
||||
# Clone test repository
|
||||
- name: Clone test repository
|
||||
git:
|
||||
repo: "{{ test_repo_url }}"
|
||||
dest: "{{ work_dir }}/{{ test_vm_name }}/test-repo"
|
||||
version: "{{ test_repo_branch }}"
|
||||
register: repo_cloned
|
||||
|
||||
- name: Find sparrowfile
|
||||
find:
|
||||
paths: "{{ work_dir }}/{{ test_vm_name }}/test-repo"
|
||||
patterns:
|
||||
- "main.raku"
|
||||
- "sparrowfile"
|
||||
recurse: true
|
||||
register: sparrowfile_search
|
||||
|
||||
- name: Validate sparrowfile exists
|
||||
fail:
|
||||
msg: "No sparrowfile or main.raku found in test repository"
|
||||
when: sparrowfile_search.files | length == 0
|
||||
|
||||
- name: Set sparrowfile path
|
||||
set_fact:
|
||||
sparrowfile_path: "{{ sparrowfile_search.files[0].path }}"
|
||||
|
||||
- name: Display sparrowfile path
|
||||
debug:
|
||||
msg: "Found sparrowfile: {{ sparrowfile_path }}"
|
||||
|
||||
# Run Sparrowdo test
|
||||
- name: Build sparrowdo command
|
||||
set_fact:
|
||||
sparrowdo_cmd: >
|
||||
sparrowdo
|
||||
--host={{ vm_ip }}
|
||||
--ssh_user={{ ssh_user }}
|
||||
--ssh_private_key={{ ssh_private_key_path }}
|
||||
--ssh_args="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
||||
{% if sparrowdo_no_sudo %}--no_sudo{% endif %}
|
||||
--sparrowfile={{ sparrowfile_path }}
|
||||
{% if sparrowdo_verbose %}--verbose{% endif %}
|
||||
{% if sparrowdo_color %}--color{% endif %}
|
||||
|
||||
- name: Create logs directory
|
||||
file:
|
||||
path: "{{ logs_dir }}/{{ test_vm_name }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
when: save_logs
|
||||
|
||||
- name: Run Sparrowdo test
|
||||
shell: "{{ sparrowdo_cmd }} 2>&1 | tee {{ logs_dir }}/{{ test_vm_name }}/test.log"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: sparrowdo_result
|
||||
timeout: "{{ sparrowdo_timeout }}"
|
||||
when: save_logs
|
||||
|
||||
- name: Run Sparrowdo test (without logging)
|
||||
command: "{{ sparrowdo_cmd }}"
|
||||
register: sparrowdo_result_nolog
|
||||
timeout: "{{ sparrowdo_timeout }}"
|
||||
when: not save_logs
|
||||
|
||||
- name: Display test result
|
||||
debug:
|
||||
msg: "Test {{ test_name }} completed successfully"
|
||||
|
||||
- name: Cleanup test VM
|
||||
include_role:
|
||||
name: cleanup_vm
|
||||
vars:
|
||||
vm_name: "{{ test_vm_name }}"
|
||||
when: cleanup_after_test
|
||||
|
||||
- name: Archive test results
|
||||
set_fact:
|
||||
test_results: "{{ test_results | default([]) + [{'name': test_name, 'status': 'passed', 'vm': test_vm_name, 'log': logs_dir + '/' + test_vm_name + '/test.log'}] }}"
|
||||
when: save_logs
|
||||
|
||||
# Error handling
|
||||
- name: Handle test failure
|
||||
block:
|
||||
- name: Archive failed test logs
|
||||
set_fact:
|
||||
test_results: "{{ test_results | default([]) + [{'name': test_name, 'status': 'failed', 'vm': test_vm_name, 'log': logs_dir + '/' + test_vm_name + '/test.log'}] }}"
|
||||
when: save_logs
|
||||
|
||||
- name: Cleanup VM on failure
|
||||
include_role:
|
||||
name: cleanup_vm
|
||||
vars:
|
||||
vm_name: "{{ test_vm_name }}"
|
||||
when: cleanup_after_test
|
||||
when: sparrowdo_result is failed or sparrowdo_result_nolog is failed
|
||||
102
ansible/tasks/run-test-with-infrastructure.yml
Normal file
102
ansible/tasks/run-test-with-infrastructure.yml
Normal file
@@ -0,0 +1,102 @@
|
||||
---
|
||||
# Provider-aware test execution with infrastructure management
|
||||
# This task file handles the complete lifecycle:
|
||||
# 1. Provision infrastructure (VM/instance based on provider)
|
||||
# 2. Run test (provider-agnostic)
|
||||
# 3. Collect results
|
||||
# 4. Cleanup infrastructure
|
||||
|
||||
- name: "Test: {{ test_item.name }}"
|
||||
block:
|
||||
# ===========================================
|
||||
# PROVISION INFRASTRUCTURE (Provider-specific)
|
||||
# ===========================================
|
||||
|
||||
# LIBVIRT: Create linked clone from golden image
|
||||
- name: "[libvirt] Provision VM from golden image"
|
||||
include_role:
|
||||
name: provision_vm
|
||||
vars:
|
||||
vm_name: "test-{{ test_item.name }}-{{ ansible_date_time.epoch }}"
|
||||
test_name: "{{ test_item.name }}"
|
||||
when: provider == 'libvirt'
|
||||
|
||||
# AWS: Launch EC2 instance (future implementation)
|
||||
- name: "[aws] Launch EC2 instance"
|
||||
debug:
|
||||
msg: "TODO: Launch EC2 instance for {{ test_item.name }}"
|
||||
when: provider == 'aws'
|
||||
# TODO: Add actual AWS provisioning role when ready
|
||||
# include_role:
|
||||
# name: provision_aws_instance
|
||||
# vars:
|
||||
# instance_name: "test-{{ test_item.name }}-{{ ansible_date_time.epoch }}"
|
||||
|
||||
# AZURE: Create VM (future implementation)
|
||||
- name: "[azure] Create Azure VM"
|
||||
debug:
|
||||
msg: "TODO: Create Azure VM for {{ test_item.name }}"
|
||||
when: provider == 'azure'
|
||||
# TODO: Add actual Azure provisioning role when ready
|
||||
|
||||
# ===========================================
|
||||
# RUN TEST (Provider-agnostic)
|
||||
# ===========================================
|
||||
|
||||
- name: "Execute test: {{ test_item.name }}"
|
||||
include_role:
|
||||
name: run_test
|
||||
vars:
|
||||
test_name: "{{ test_item.name }}"
|
||||
test_repo_url: "{{ test_item.repo_url }}"
|
||||
test_repo_branch: "{{ test_item.branch | default(default_test_branch) }}"
|
||||
test_timeout: "{{ test_item.timeout | default(default_test_timeout) }}"
|
||||
|
||||
# Record success
|
||||
- name: Record test success
|
||||
set_fact:
|
||||
test_results: "{{ test_results + [{'name': test_item.name, 'status': 'passed'}] }}"
|
||||
|
||||
rescue:
|
||||
# Record failure
|
||||
- name: Record test failure
|
||||
set_fact:
|
||||
test_results: "{{ test_results + [{'name': test_item.name, 'status': 'failed', 'error': ansible_failed_result.msg | default('Unknown error')}] }}"
|
||||
|
||||
- name: Display test failure
|
||||
debug:
|
||||
msg: "Test {{ test_item.name }} FAILED: {{ ansible_failed_result.msg | default('Unknown error') }}"
|
||||
|
||||
always:
|
||||
# ===========================================
|
||||
# CLEANUP INFRASTRUCTURE (Provider-specific)
|
||||
# ===========================================
|
||||
|
||||
# LIBVIRT: Destroy VM clone
|
||||
- name: "[libvirt] Cleanup VM"
|
||||
include_role:
|
||||
name: cleanup_vm
|
||||
vars:
|
||||
vm_name: "test-{{ test_item.name }}-{{ ansible_date_time.epoch }}"
|
||||
when: provider == 'libvirt'
|
||||
ignore_errors: true
|
||||
|
||||
# AWS: Terminate EC2 instance (future implementation)
|
||||
- name: "[aws] Terminate EC2 instance"
|
||||
debug:
|
||||
msg: "TODO: Terminate EC2 instance for {{ test_item.name }}"
|
||||
when: provider == 'aws'
|
||||
ignore_errors: true
|
||||
# TODO: Add actual AWS cleanup role when ready
|
||||
|
||||
# AZURE: Delete VM (future implementation)
|
||||
- name: "[azure] Delete Azure VM"
|
||||
debug:
|
||||
msg: "TODO: Delete Azure VM for {{ test_item.name }}"
|
||||
when: provider == 'azure'
|
||||
ignore_errors: true
|
||||
# TODO: Add actual Azure cleanup role when ready
|
||||
|
||||
- name: Test completed
|
||||
debug:
|
||||
msg: "Finished test: {{ test_item.name }}"
|
||||
23
ansible/vars/test-definitions.yml
Normal file
23
ansible/vars/test-definitions.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
default_test_branch: "main" # Default git branch
|
||||
default_test_timeout: 300 # Default timeout in seconds (15 min)
|
||||
default_test_filter: ".*" # Regex filter (matches all tests)
|
||||
|
||||
# Test execution behavior
|
||||
retry_failed_tests: false
|
||||
continue_on_failure: true # Keep running tests even if one fails
|
||||
|
||||
# ===========================================
|
||||
# TEST DEFINITIONS
|
||||
# ===========================================
|
||||
# Required fields:
|
||||
# - name: Unique test identifier
|
||||
# - repo_url: Git repository URL
|
||||
#
|
||||
# Optional fields (uses defaults above if omitted):
|
||||
# - branch: Git branch (default: main)
|
||||
# - timeout: Test timeout in seconds (default: 300)
|
||||
|
||||
tests:
|
||||
- name: "Sparky_Knot"
|
||||
repo_url: "https://git.resf.org/testing/Sparky_Knot.git"
|
||||
Reference in New Issue
Block a user