Skip to content

Commit 38347f5

Browse files
committed
Merge remote-tracking branch 'origin/main' into grails7-upgrade
2 parents 935392d + 23cdf4b commit 38347f5

30 files changed

Lines changed: 2645 additions & 105 deletions

File tree

functional-test/README.md

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# Functional Tests for Ansible Plugin
2+
3+
This directory contains functional tests for the Rundeck Ansible plugin using Testcontainers and Docker.
4+
5+
## Prerequisites
6+
7+
- Docker (Docker Desktop or Rancher Desktop)
8+
- Java 11 or later
9+
- Gradle 7.2 or later
10+
11+
## Docker Configuration
12+
13+
The functional tests use Testcontainers to spin up Docker containers for Rundeck and SSH nodes. Depending on your Docker setup, you may need to configure the Docker socket path.
14+
15+
### Option 1: Using ~/.testcontainers.properties (Recommended)
16+
17+
Create a file at `~/.testcontainers.properties` with the following content:
18+
19+
**For Rancher Desktop on macOS:**
20+
```properties
21+
docker.host=unix:///Users/<your-username>/.rd/docker.sock
22+
```
23+
24+
**For Docker Desktop on macOS/Linux:**
25+
```properties
26+
docker.host=unix:///var/run/docker.sock
27+
```
28+
29+
**For Windows with Docker Desktop:**
30+
```properties
31+
docker.host=tcp://localhost:2375
32+
```
33+
34+
### Option 2: Modify build.gradle
35+
36+
Edit `functional-test/build.gradle` and update the `docker.host` paths on lines 52-53:
37+
38+
```gradle
39+
systemProperty('docker.host', "unix:///path/to/your/docker.sock")
40+
environment 'DOCKER_HOST', 'unix:///path/to/your/docker.sock'
41+
```
42+
43+
## Running the Tests
44+
45+
### Run All Functional Tests
46+
47+
```bash
48+
./gradlew :functional-test:functionalTest
49+
```
50+
51+
### Run Specific Test Suite
52+
53+
```bash
54+
# Multi-node authentication tests
55+
./gradlew :functional-test:functionalTest --tests "*MultiNodeAuthSpec*"
56+
57+
# Basic integration tests
58+
./gradlew :functional-test:functionalTest --tests "*BasicIntegrationSpec*"
59+
60+
# Plugin group tests
61+
./gradlew :functional-test:functionalTest --tests "*PluginGroupIntegrationSpec*"
62+
```
63+
64+
## Test Suites
65+
66+
### MultiNodeAuthSpec
67+
Tests the multi-node authentication feature where each node can have its own password stored in Rundeck's key storage.
68+
69+
**Tests:**
70+
- `test ansible playbook with multi-node authentication` - Verifies Ansible playbooks execute across multiple nodes with per-node credentials
71+
- `test multi-node authentication with different passwords` - Tests script execution on multiple nodes with different passwords
72+
- `test nodes are accessible with different credentials` - Validates node registration and discovery
73+
- `test passwords with special characters are properly escaped` - Verifies YAML escaping for special characters in passwords
74+
75+
**Test Environment:**
76+
- 3 SSH nodes (ssh-node, ssh-node-2, ssh-node-3)
77+
- Each node has a different password, including special characters
78+
- Tests both WorkflowStep (Ansible playbooks) and NodeStep (scripts) execution
79+
80+
### BasicIntegrationSpec
81+
Basic integration tests for the Ansible plugin functionality.
82+
83+
### PluginGroupIntegrationSpec
84+
Tests for plugin group configuration and execution.
85+
86+
## Test Structure
87+
88+
```
89+
functional-test/
90+
├── build.gradle # Test configuration
91+
├── README.md # This file
92+
└── src/
93+
└── test/
94+
├── groovy/functional/ # Test specifications
95+
│ ├── MultiNodeAuthSpec.groovy
96+
│ ├── BasicIntegrationSpec.groovy
97+
│ └── PluginGroupIntegrationSpec.groovy
98+
└── resources/
99+
├── docker/ # Docker compose and configs
100+
│ ├── docker-compose.yml
101+
│ ├── ansible-multi-node-auth/ # Multi-node test configs
102+
│ ├── keys/ # SSH keys for tests
103+
│ ├── node/ # SSH node Docker configs
104+
│ └── rundeck/ # Rundeck Docker configs
105+
└── project-import/ # Rundeck project definitions
106+
└── ansible-multi-node-auth/
107+
└── rundeck-ansible-multi-node-auth/
108+
├── files/etc/project.properties
109+
└── jobs/*.xml
110+
```
111+
112+
## Troubleshooting
113+
114+
### Tests Fail with "Could not find Docker socket"
115+
116+
**Problem:** Testcontainers cannot locate the Docker socket.
117+
118+
**Solution:**
119+
1. Verify Docker is running: `docker ps`
120+
2. Check your Docker socket path:
121+
- Rancher Desktop: `ls -la ~/.rd/docker.sock`
122+
- Docker Desktop: `ls -la /var/run/docker.sock`
123+
3. Update `~/.testcontainers.properties` or `build.gradle` with the correct path
124+
125+
### Tests Timeout or Hang
126+
127+
**Problem:** Tests take too long or appear to hang.
128+
129+
**Solution:**
130+
1. Check Docker container status: `docker ps -a`
131+
2. Check Docker logs: `docker logs <container-id>`
132+
3. Increase test timeout in build.gradle if needed
133+
4. Ensure sufficient Docker resources (memory/CPU)
134+
135+
### Port Conflicts
136+
137+
**Problem:** Tests fail with "port already in use" errors.
138+
139+
**Solution:**
140+
1. Check for running containers: `docker ps`
141+
2. Stop conflicting containers: `docker stop <container-name>`
142+
3. Clean up: `docker-compose down` in the docker directory
143+
144+
### Platform Mismatch Warnings (Apple Silicon)
145+
146+
**Problem:** Warnings about platform mismatch (linux/amd64 vs linux/arm64).
147+
148+
**Solution:** These warnings are expected on Apple Silicon Macs and can be safely ignored. Docker will use Rosetta 2 for emulation.
149+
150+
## Test Reports
151+
152+
After running tests, view the HTML report at:
153+
```
154+
functional-test/build/reports/tests/functionalTest/index.html
155+
```
156+
157+
## Adding New Tests
158+
159+
1. Create a new Spock specification in `src/test/groovy/functional/`
160+
2. Extend `BaseTestConfiguration` for common test utilities
161+
3. Add any required Docker configs to `src/test/resources/docker/`
162+
4. Add project imports to `src/test/resources/project-import/`
163+
5. Follow existing test patterns for consistency
164+
165+
## Multi-Node Authentication Feature
166+
167+
The multi-node authentication feature allows running Ansible playbooks across multiple nodes where each node has its own password stored in Rundeck's key storage.
168+
169+
**How it works:**
170+
1. Enable at project level: `project.ansible-generate-inventory-nodes-auth=true`
171+
2. Store per-node passwords in Key Storage with paths specified in node attributes
172+
3. Plugin generates `group_vars/all.yaml` with vault-encrypted passwords
173+
4. Ansible uses host-specific credentials from group_vars
174+
175+
**Requirements:**
176+
- Must use Ansible Playbook **Workflow Steps** (not Node Steps); the multi-node authentication logic and inventory generation are only implemented for the Workflow Step variant of the plugin and are not executed for Node Steps, so enabling `project.ansible-generate-inventory-nodes-auth` while using Node Steps will not apply per-node credentials. This limitation exists because Node Steps run independently on each target node and do not share the global inventory context that the multi-node authentication feature relies on.
177+
- Node attributes must include `ansible-ssh-password-storage-path` for each node
178+
- Passwords are automatically encrypted using Ansible Vault
179+
- Supports special characters with proper YAML escaping
180+
181+
See `MultiNodeAuthSpec.groovy` for comprehensive test examples.

functional-test/build.gradle

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,20 @@ dependencies {
4242

4343
tasks.register('functionalTest', Test) {
4444
useJUnitPlatform()
45-
systemProperty('RUNDECK_TEST_IMAGE', "rundeck/rundeck:5.1.1")
45+
46+
// Rundeck test image version
47+
// Minimum supported Rundeck version for this plugin: 5.1.1 (see main README/changelog).
48+
// Functional tests intentionally run against a newer version, Rundeck 5.18.0, to validate
49+
// compatibility with current releases. When raising the minimum supported version, update
50+
// this test image tag in tandem.
51+
systemProperty('RUNDECK_TEST_IMAGE', "rundeck/rundeck:5.18.0")
52+
53+
// Docker configuration for Testcontainers
54+
// For Rancher Desktop on macOS: Use /Users/<username>/.rd/docker.sock
55+
// For Docker Desktop on macOS: Use /var/run/docker.sock
56+
// For Linux: Use /var/run/docker.sock
57+
// You can also configure this via ~/.testcontainers.properties (see functional-test/README.md)
58+
4659
description = "Run Ansible integration tests"
4760
}
4861

functional-test/src/test/groovy/functional/InventoryListSpec.groovy

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ class InventoryListSpec extends BaseTestConfiguration {
2222
result!=null
2323
result.size()==8000
2424
result.get("Node-0")!=null
25-
result.get("Node-0").getAttributes().get("ansible_host") == "ssh-node"
26-
result.get("Node-0").getAttributes().get("ansible_ssh_user") == "rundeck"
25+
// ansible_ prefixed variables should be filtered out, not in attributes
26+
result.get("Node-0").getAttributes().get("ansible_host") == null
27+
result.get("Node-0").getAttributes().get("ansible_ssh_user") == null
28+
// custom variables should still be imported
2729
result.get("Node-0").getAttributes().get("some-var") == "1234"
2830
result.get("Node-7999")!=null
29-
result.get("Node-7999").getAttributes().get("ansible_host") == "ssh-node"
30-
result.get("Node-7999").getAttributes().get("ansible_ssh_user") == "rundeck"
31+
result.get("Node-7999").getAttributes().get("ansible_host") == null
32+
result.get("Node-7999").getAttributes().get("ansible_ssh_user") == null
3133
result.get("Node-7999").getAttributes().get("some-var") == "1234"
3234
}
3335

@@ -40,8 +42,9 @@ class InventoryListSpec extends BaseTestConfiguration {
4042
result!=null
4143
result.size()==35
4244
result.get("node1")!=null
43-
result.get("node1").getAttributes().get("ansible_host") == "node1"
44-
result.get("node1").getAttributes().get("ansible_user") == "agent"
45-
result.get("node1").getAttributes().get("ansible_port") == "22"
45+
// ansible_ prefixed variables should be filtered out, not in attributes
46+
result.get("node1").getAttributes().get("ansible_host") == null
47+
result.get("node1").getAttributes().get("ansible_user") == null
48+
result.get("node1").getAttributes().get("ansible_port") == null
4649
}
4750
}

0 commit comments

Comments
 (0)