一键导入
molecule-testing
Run and validate Ansible tests. Molecule commands, TDD workflow, layered scenarios, performance optimization.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Run and validate Ansible tests. Molecule commands, TDD workflow, layered scenarios, performance optimization.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | molecule-testing |
| description | Run and validate Ansible tests. Molecule commands, TDD workflow, layered scenarios, performance optimization. |
molecule converge && molecule verify for day-to-day iteration. Full molecule test is for clean-state proof ONLY.build-images.sh --only <target> (~2 min) + molecule test -s <type>-lxc (~5 min) is the fast loop.molecule test) should be fast because images are already cached. If it takes 30+ minutes, the bake pipeline is broken (re-uploading or runtime-installing).source .venv/bin/activate
set -a; source test.env; set +a
molecule test # full pipeline
molecule converge # run playbook (preserves baseline)
molecule verify # run assertions
molecule cleanup # reset host
CRITICAL: Always use set -a; source test.env; set +a before running molecule. This ensures all environment variables are exported and available to Ansible.
Variable export: The set -a flag automatically exports all variables from the sourced file, making them available to child processes.
Molecule env handling: Molecule provisioner uses ${VAR} syntax in molecule.yml. Environment variables must be properly exported for molecule to access them.
Previous bug: molecule test failed with "Could not resolve hostname none" because environment variables weren't exported. Solution: set -a && source test.env && set +a.
Test environment validation:
# Verify environment is loaded
echo "HOME_API_TOKEN: $HOME_API_TOKEN"
echo "PRIMARY_HOST: $PRIMARY_HOST"
# If base state established, use heartbeat system:
curl -sf http://localhost:$WEBUI_PORT/api/fleet/health && echo "Fleet OK"
# If pre-base-state (first run), verify Ansible access:
ansible home -m ping
# FAST iteration (primary loop — use this 90% of the time)
molecule converge # build/update baseline
molecule verify # run assertions
# Per-image fix (when ONE image is broken)
./build-images.sh --host $PRIMARY_HOST --only pihole # ~2-3 min
molecule test -s pihole-lxc # per-feature only
# Layered scenario (after baseline exists)
molecule converge -s wireguard-lxc # run layered scenario
molecule verify -s wireguard-lxc
# Full E2E clean-state proof (LAST STEP — after all images pass)
molecule test
Pre-commit: molecule test
The 6 test hosts are a STRENGTH. Use them for parallel image builds. Never wait for a full E2E to discover a single-image bug.
build-images.sh for the broken targetmolecule test runs: dependency → cleanup → syntax → prepare → converge → verify
There is NO trailing cleanup or converge. The baseline is left running after
verify. NEVER add cleanup or destroy to the end of the test_sequence —
that tears down OpenWrt and makes mesh1 permanently unreachable.
The prepare phase starts the callhome API server and writes
.state/callhome_url. The cleanup phase at the beginning resets the
host from any previous run.
No lint phase — run ansible-lint && yamllint . separately.
Layered scenarios (e.g., openwrt-security):
Standalone scenarios (e.g., proxmox-igpu):
set -a; source test.env; set +a
ssh root@$PRIMARY_HOST hostname
./build-images.sh
Before any molecule work, ALWAYS verify environment:
# Verify environment variables are loaded
echo "HOME_API_TOKEN: $HOME_API_TOKEN"
echo "PRIMARY_HOST: $PRIMARY_HOST"
# If base state is already established, check the heartbeat system:
curl -sf http://localhost:$WEBUI_PORT/api/fleet/health && echo "Fleet OK"
curl -sf http://localhost:$WEBUI_PORT/api/nodes | python3 -m json.tool
# If pre-base-state (first run), verify Ansible management access:
ansible home -m ping
CRITICAL: If the SM API is not responding (and base state should be up), that means the heart stopped. Fix the SM. If any of these fail, DO NOT proceed with molecule commands.
Test IMMEDIATELY when:
pct exec or container commands are addedTest IMMEDIATELY when you see:
proxmox_vmid vs homeassistant_ct_id)platforms:
- name: home
groups: [proxmox, router_nodes, vpn_nodes, wifi_nodes]
- name: mesh1
groups: [proxmox, lan_hosts, vpn_nodes, wifi_nodes]
All 4 hosts in default scenario. Without this, plays skip hosts.
Per-feature scenarios target dynamic groups. State does NOT persist across phases. MUST reconstruct at start of each phase:
- name: Reconstruct openwrt group
hosts: router_nodes
tasks:
- include_tasks: ../../tasks/reconstruct_openwrt_group.yml
Task file detects OpenWrt LAN IP, auth method, builds SSH args, registers host.
Use role_path for project-relative paths:
src: "{{ role_path }}/../../images/{{ lxc_ct_template }}"
When working on new service types, read relevant skills before writing code:
lxc-container-patterns (provisioning, networking, Docker-in-LXC)proxmox-safety-rules, proxmox-system-safetyansible-conventions, ansible-shell-safetyservice-config-validationPrevious lesson: Docker-in-LXC configure play targeted the container dynamic group instead of the host group. The lxc-container-patterns skill now documents this pattern explicitly.
cache_valid_time: 86400 for apt taskspct_remote tasks. Each opens new SSH connectionpct config reads — one call per containerMolecule provisioner.env uses ${VAR} syntax. NEVER use ${VAR:-default}.
Required: add to provisioner.env. Optional: do NOT add (role defaults use lookup('env', 'VAR')).
ansible.builtin.raw: heredocs fail on Jinja2-like content:
${var:-default} — misinterpreted|| true — can confuse parser[:space:] — colons conflict with YAMLRun ansible-playbook --syntax-check playbooks/site.yml after modifying raw:.
FAILED, fatal:, UNREACHABLEdmesg | grep -iE 'error|segfault|duplicate'ip addr, ip routeansible.builtin.shell:
cmd: |
set -o pipefail
command1 | command2
executable: /bin/bash
Exception: ansible.builtin.raw and BusyBox do NOT support pipefail.
Use delegate_to: localhost instead of local_action. Use FQCNs: ansible.builtin.command.
| Symptom | Cause | Fix |
|---|---|---|
UNREACHABLE during converge | SSH broken or host down | Check PRIMARY_HOST, verify SSH |
community.proxmox not found | Collections missing | ansible-galaxy collection install -r requirements.yml |
| Bridge numbers keep incrementing | Cleanup didn't remove bridges | ./cleanup.sh clean test.env |
| WiFi radios=0 after converge | PCI passthrough not cleaned up | Ensure cleanup unbinds vfio-pci, reloads modules, rescans PCI |
Timeout waiting for SSH | Network restart dropped connection | Verify SSH args include ConnectTimeout=10, ServerAliveInterval=15 |
opkg update fails HTTPS | HTTPS not supported | Ensure sed -i 's|https://|http://|g' runs before opkg update |
opkg update EPERM | Firewall zones stale after network restart | Restart firewall before outbound connections |
| MAC stored without colons in UCI | BusyBox tr -d '[:space:]' deletes : | Use tr -d ' \t\n\r' |
| GUI reachability fails on OpenWrt | BusyBox nc no -w flag | Use (echo QUIT | nc HOST PORT) </dev/null |
| VM reachable by IPv6 but not IPv4 | Stale LAN-subnet IP on another bridge | Remove conflicting IPs from non-LAN bridges |
ifreload -a no DHCP client | Separate inet dhcp file conflicts | Modify bridge stanza in-place |
| Route filter hides default route | ip route show default dev eth0 misses aliases | Use ip route show default without dev filter |
igpu_available not defined in verify | Facts from converge not in verify | Re-include role in verify.yml |
Could not find or access template | Relative path from scenario dir | Use role_path for paths |
ModuleNotFoundError: paramiko | Missing Python dep for pct_remote | Add paramiko to requirements.txt |
apt-get update hangs on Proxmox | Enterprise repos unreachable | Rename to .disabled, add no-subscription repo |
lsmod | grep -q returns rc=141 | SIGPIPE from grep -q with pipefail | Use grep -c instead |
| Per-feature verify passes with 0 assertions | Dynamic group empty | Add group reconstruction play |
| Rollback targets 0 hosts | Dynamic group empty in cleanup | Add reconstruction play in cleanup.yml |
| SSH auth fails after security rollback | Didn't clear root password | Rollback MUST clear /etc/shadow root hash |
uci: Invalid argument on mesh radio | PHY namespace-moved after boot | Run wifi config before uci set wireless.radio* |
changed_when: false and failed_when: false on diagnostic tasksdebug: var: to display in logsdmesg errors) at key milestonesansible-lint: .ansible-lint — production profile, skips command-instead-of-module for Proxmox shell tasksyamllint: .yamllint.yml — 160-char lines, relaxed comment spacingansible-lint && yamllint .Project architecture and design principles for vm_builds Ansible project. Includes bake vs configure patterns, two-role service model, and deployment lifecycle.
Python code conventions. Functions return errors, .env parsing strips quotes, type hints required.
Proactive validation patterns to catch issues early, prevent debugging blind, and establish testing habits before problems escalate.
Update skills and rules when encountering new issues to prevent recurrence. Includes hard-fail patterns, code custodianship, and mandatory testing requirements.
LXC container provisioning and configuration patterns. Use when creating LXC services, managing container networking, or handling LXC template operations.
Molecule test performance optimization patterns. Template caching, NTP sync, pct_remote overhead, apt cache, selective rebuilds.