Ansible Validator Overview Comprehensive toolkit for validating, linting, and testing Ansible playbooks, roles, and collections. This skill provides automated workflows for ensuring Ansible code quality, syntax validation, dry-run testing with check mode and molecule, and intelligent documentation lookup for custom modules and collections with version awareness. Default behavior: When validating any Ansible role with a directory, attempt Molecule automatically using . If Molecule cannot run due to environment/runtime limits, mark Molecule as , report why, and continue all non-Molecule validat…

)\n\n# Better: Use modules instead of shell/command\n- name: Create directory\n file:\n path: \"{{ directory_name }}\"\n state: directory\n mode: '0755'\n\n# Validate input before use\n- name: Search logs\n command: \"grep {{ search_term }} /var/log/app.log\"\n when:\n - search_term is defined\n - search_term | length > 0\n - search_term is match('^[a-zA-Z0-9 ]+

Ansible Validator Overview Comprehensive toolkit for validating, linting, and testing Ansible playbooks, roles, and collections. This skill provides automated workflows for ensuring Ansible code quality, syntax validation, dry-run testing with check mode and molecule, and intelligent documentation lookup for custom modules and collections with version awareness. Default behavior: When validating any Ansible role with a directory, attempt Molecule automatically using . If Molecule cannot run due to environment/runtime limits, mark Molecule as , report why, and continue all non-Molecule validat…

)\n args:\n warn: false\n\n# Use args for command parameters\n- name: Run script with arguments\n command: /usr/local/bin/script.sh\n args:\n stdin: \"{{ user_input }}\"\n```\n\n### Best Practices\n\n1. **Prefer modules over command/shell** whenever possible\n2. **Always use quote filter** for variables in shell commands\n3. **Validate input** with regex patterns\n4. **Use whitelist validation** not blacklist\n5. **Never trust user input** without validation\n\n## Network Security\n\n### ❌ Bad Practices\n\n```yaml\n# Unencrypted protocols\n- name: Download file\n get_url:\n url: http://example.com/file.tar.gz # WRONG: HTTP not HTTPS\n dest: /tmp/file.tar.gz\n\n# Disabled SSL verification\n- name: Call API\n uri:\n url: https://api.example.com/data\n validate_certs: no # WRONG: Disables security\n\n# Exposing on all interfaces unnecessarily\n- name: Configure service\n template:\n src: config.j2\n dest: /etc/app/config.yml\n vars:\n bind_address: \"0.0.0.0\" # RISKY: Expose to all\n```\n\n### ✅ Good Practices\n\n```yaml\n# Use HTTPS\n- name: Download file\n get_url:\n url: https://example.com/file.tar.gz\n dest: /tmp/file.tar.gz\n checksum: sha256:abc123...\n\n# Validate SSL certificates\n- name: Call API\n uri:\n url: https://api.example.com/data\n validate_certs: yes\n client_cert: /path/to/cert.pem\n client_key: /path/to/key.pem\n\n# Bind to specific interface\n- name: Configure service\n template:\n src: config.j2\n dest: /etc/app/config.yml\n vars:\n bind_address: \"127.0.0.1\" # Localhost only\n\n# Use firewall rules\n- name: Configure firewall\n ufw:\n rule: allow\n port: '443'\n proto: tcp\n src: '10.0.0.0/8' # Only from internal network\n```\n\n### Best Practices\n\n1. **Always use HTTPS** for external communications\n2. **Validate SSL certificates** - only disable for testing\n3. **Bind services to specific interfaces** when possible\n4. **Use firewall rules** to restrict access\n5. **Encrypt sensitive data in transit** (TLS/SSL)\n\n## SELinux and AppArmor\n\n### Best Practices\n\n```yaml\n# Don't disable SELinux\n- name: Configure SELinux\n selinux:\n policy: targeted\n state: enforcing # Not permissive or disabled\n\n# Set proper SELinux contexts\n- name: Set SELinux context for web content\n sefcontext:\n target: '/web/content(/.*)?'\n setype: httpd_sys_content_t\n state: present\n\n- name: Apply SELinux context\n command: restorecon -Rv /web/content\n\n# Manage AppArmor profiles\n- name: Load AppArmor profile\n command: apparmor_parser -r /etc/apparmor.d/usr.bin.myapp\n```\n\n## Audit and Logging\n\n### Best Practices\n\n```yaml\n# Log security-relevant actions\n- name: Create admin user\n user:\n name: admin\n groups: sudo\n state: present\n register: admin_user_result\n\n- name: Log user creation\n lineinfile:\n path: /var/log/ansible-changes.log\n line: \"{{ ansible_date_time.iso8601 }} - Admin user created by {{ ansible_user_id }}\"\n create: yes\n when: admin_user_result.changed\n\n# Use tags for security-related tasks\n- name: Configure SSH\n template:\n src: sshd_config.j2\n dest: /etc/ssh/sshd_config\n tags:\n - security\n - ssh\n```\n\n## Security Validation Checklist\n\nBefore running playbooks in production, verify:\n\n- [ ] No hardcoded secrets (passwords, API keys, tokens)\n- [ ] All sensitive data encrypted with Ansible Vault\n- [ ] `no_log: true` used for tasks handling secrets\n- [ ] Privilege escalation only where necessary\n- [ ] File permissions explicitly set (not relying on umask)\n- [ ] Private keys have mode 0600\n- [ ] No world-writable files or directories\n- [ ] Input validation for user-provided variables\n- [ ] Using modules instead of shell/command where possible\n- [ ] Quote filter used for variables in shell commands\n- [ ] HTTPS used instead of HTTP\n- [ ] SSL certificate validation enabled\n- [ ] Services bound to specific interfaces, not 0.0.0.0\n- [ ] Firewall rules configured appropriately\n- [ ] SELinux/AppArmor not disabled\n- [ ] Security contexts set correctly\n- [ ] Security-relevant actions logged\n- [ ] Regular security updates applied\n- [ ] Unused packages removed\n- [ ] Default credentials changed\n- [ ] Unnecessary services disabled\n\n## Tools for Security Scanning\n\n1. **ansible-lint** - Includes security-focused rules\n ```bash\n ansible-lint --profile security playbook.yml\n ```\n\n2. **Ansible Galaxy Security Scan**\n ```bash\n ansible-galaxy collection scan namespace.collection\n ```\n\n3. **Git-secrets** - Prevent committing secrets\n ```bash\n git secrets --scan\n ```\n\n4. **Trivy** - Scan for vulnerabilities\n ```bash\n trivy config .\n ```\n\n## Additional Resources\n\n- [Ansible Security Automation](https://www.ansible.com/use-cases/security-automation)\n- [Ansible Best Practices - Security](https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html#best-practices-for-variables-and-vaults)\n- [OWASP Top 10](https://owasp.org/www-project-top-ten/)\n- [CIS Benchmarks](https://www.cisecurity.org/cis-benchmarks/)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":11052,"content_sha256":"1610a24498e8fb14b679dd15a00bed060529c79a74df06765427e9319c757386"},{"filename":"scripts/check_fqcn.sh","content":"#!/usr/bin/env bash\n\n# Ansible FQCN (Fully Qualified Collection Name) Checker\n# Identifies modules using short names instead of FQCN format\n# Recommends migration to ansible.builtin.* or appropriate collection\n\nset -e\n\nTARGET=\"$1\"\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nSKILL_DIR=\"$(dirname \"$SCRIPT_DIR\")\"\n\nCOLOR_GREEN='\\033[0;32m'\nCOLOR_YELLOW='\\033[1;33m'\nCOLOR_RED='\\033[0;31m'\nCOLOR_BLUE='\\033[0;34m'\nCOLOR_CYAN='\\033[0;36m'\nCOLOR_RESET='\\033[0m'\n\n# Usage check\nif [ -z \"$TARGET\" ]; then\n echo \"Usage: $0 \u003cplaybook.yml|role-directory|directory>\"\n echo \"\"\n echo \"Scans Ansible files for modules using short names instead of FQCN.\"\n echo \"Recommends migration to fully qualified collection names for better\"\n echo \"clarity and future compatibility.\"\n exit 1\nfi\n\nif [ ! -f \"$TARGET\" ] && [ ! -d \"$TARGET\" ]; then\n echo -e \"${COLOR_RED}Error: Target not found: $TARGET${COLOR_RESET}\"\n exit 1\nfi\n\n# Get absolute path\nif [ -f \"$TARGET\" ]; then\n TARGET_ABS=$(cd \"$(dirname \"$TARGET\")\" && pwd)/$(basename \"$TARGET\")\n SCAN_TYPE=\"file\"\nelse\n TARGET_ABS=$(cd \"$TARGET\" && pwd)\n SCAN_TYPE=\"directory\"\nfi\n\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}Ansible FQCN Module Checker${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho \"\"\necho \"Scanning: $TARGET_ABS\"\necho \"\"\n\n# Common ansible.builtin modules that are often used with short names\nBUILTIN_MODULES=(\n \"apt\"\n \"yum\"\n \"dnf\"\n \"package\"\n \"pip\"\n \"copy\"\n \"file\"\n \"template\"\n \"lineinfile\"\n \"blockinfile\"\n \"replace\"\n \"fetch\"\n \"stat\"\n \"get_url\"\n \"uri\"\n \"service\"\n \"systemd\"\n \"user\"\n \"group\"\n \"command\"\n \"shell\"\n \"raw\"\n \"script\"\n \"debug\"\n \"set_fact\"\n \"assert\"\n \"fail\"\n \"include\"\n \"include_tasks\"\n \"include_vars\"\n \"import_tasks\"\n \"import_playbook\"\n \"import_role\"\n \"include_role\"\n \"pause\"\n \"wait_for\"\n \"wait_for_connection\"\n \"meta\"\n \"add_host\"\n \"group_by\"\n \"cron\"\n \"git\"\n \"setup\"\n \"gather_facts\"\n \"ping\"\n \"reboot\"\n \"hostname\"\n \"sysctl\"\n \"mount\"\n \"unarchive\"\n \"archive\"\n \"find\"\n \"slurp\"\n \"known_hosts\"\n \"authorized_key\"\n \"firewalld\"\n \"iptables\"\n \"selinux\"\n \"seboolean\"\n)\n\n# Modules from common collections\nCOMMUNITY_GENERAL_MODULES=(\n \"ufw\"\n \"homebrew\"\n \"zypper\"\n \"apk\"\n \"npm\"\n \"gem\"\n \"composer\"\n \"docker_container\"\n \"docker_image\"\n \"docker_network\"\n \"docker_volume\"\n \"docker_compose\"\n \"timezone\"\n \"locale_gen\"\n \"alternatives\"\n \"sysvinit\"\n \"jenkins_job\"\n \"jenkins_plugin\"\n \"nagios\"\n \"zabbix_host\"\n \"grafana_dashboard\"\n \"htpasswd\"\n \"lvol\"\n \"lvg\"\n \"parted\"\n \"filesystem\"\n)\n\nANSIBLE_POSIX_MODULES=(\n \"synchronize\"\n \"acl\"\n \"authorized_key\"\n \"firewalld\"\n \"selinux\"\n \"seboolean\"\n \"at\"\n \"mount\"\n \"sysctl\"\n)\n\nNON_FQCN_FOUND=0\ndeclare -A FOUND_MODULES\n\nscan_inline_value_module_file() {\n local module=\"$1\"\n local file_path=\"$2\"\n local implicit_tasks=0\n\n # Task/handler files are usually top-level task lists without a tasks: key.\n if [[ \"$file_path\" == *\"/tasks/\"* ]] || [[ \"$file_path\" == *\"/handlers/\"* ]]; then\n implicit_tasks=1\n fi\n\n awk -v module=\"$module\" -v implicit_tasks=\"$implicit_tasks\" '\n function indent_len(text) {\n match(text, /^[[:space:]]*/)\n return RLENGTH\n }\n\n BEGIN {\n in_task_section = 0\n task_section_indent = -1\n task_item_indent = -1\n }\n\n {\n line = $0\n\n if (line ~ /^[[:space:]]*$/ || line ~ /^[[:space:]]*#/) {\n next\n }\n\n current_indent = indent_len(line)\n\n # Track explicit task-bearing sections in playbooks.\n if (line ~ /^[[:space:]]*(tasks|pre_tasks|post_tasks|handlers|block|rescue|always):[[:space:]]*(#.*)?$/) {\n in_task_section = 1\n task_section_indent = current_indent\n task_item_indent = -1\n next\n }\n\n if (in_task_section && current_indent \u003c= task_section_indent && line !~ /^[[:space:]]*-[[:space:]]/) {\n in_task_section = 0\n task_section_indent = -1\n task_item_indent = -1\n }\n\n if (line ~ /^[[:space:]]*-[[:space:]]/) {\n task_item_indent = current_indent\n }\n\n module_pattern = \"^[[:space:]]+\" module \":[[:space:]]+[^#[:space:]].*$\"\n if (line ~ module_pattern) {\n if ((in_task_section || implicit_tasks == 1) &&\n task_item_indent >= 0 &&\n current_indent == (task_item_indent + 2)) {\n printf(\"%d:%s\\n\", NR, line)\n }\n }\n }\n ' \"$file_path\" 2>/dev/null || true\n}\n\nscan_inline_value_module_directory() {\n local module=\"$1\"\n local directory=\"$2\"\n\n while IFS= read -r yaml_file; do\n local file_matches\n file_matches=$(scan_inline_value_module_file \"$module\" \"$yaml_file\")\n if [ -n \"$file_matches\" ]; then\n while IFS= read -r line_match; do\n [ -n \"$line_match\" ] || continue\n printf '%s:%s\\n' \"$yaml_file\" \"$line_match\"\n done \u003c\u003c\u003c \"$file_matches\"\n fi\n done \u003c \u003c(find \"$directory\" -type f \\( -name \"*.yml\" -o -name \"*.yaml\" \\) ! -path \"*/.git/*\" 2>/dev/null | sort)\n}\n\n# Function to check for non-FQCN module usage\ncheck_module() {\n local module=\"$1\"\n local fqcn=\"$2\"\n local collection=\"$3\"\n local results=\"\"\n\n if [ \"$SCAN_TYPE\" = \"file\" ]; then\n # Pattern 1: module starts the task item (has a leading dash)\n # e.g. \" - apt:\" or \" - shell: cmd\"\n local r1\n r1=$(grep -n -E \"^\\s*-\\s+${module}:\" \"$TARGET_ABS\" 2>/dev/null | grep -v \"${fqcn}\" || true)\n # Pattern 2: module key alone on its line (no value = dict form, after name:)\n # e.g. \" apt:\" — but NOT \" group: root\" (parameter with a value)\n local r2\n r2=$(grep -n -E \"^\\s+${module}:\\s*(#.*)?$\" \"$TARGET_ABS\" 2>/dev/null | grep -v \"${fqcn}\" || true)\n # Pattern 3: inline-value form under task items, e.g. \" shell: echo hello\"\n local r3\n r3=$(scan_inline_value_module_file \"$module\" \"$TARGET_ABS\")\n results=$(printf '%s\\n%s\\n%s' \"$r1\" \"$r2\" \"$r3\" | grep -v \"^$\" | sort -u || true)\n else\n local r1\n r1=$(grep -r -n -E \"^\\s*-\\s+${module}:\" \"$TARGET_ABS\" --include=\"*.yml\" --include=\"*.yaml\" 2>/dev/null | grep -v \".git/\" | grep -v \"${fqcn}\" || true)\n local r2\n r2=$(grep -r -n -E \"^\\s+${module}:\\s*(#.*)?$\" \"$TARGET_ABS\" --include=\"*.yml\" --include=\"*.yaml\" 2>/dev/null | grep -v \".git/\" | grep -v \"${fqcn}\" || true)\n local r3\n r3=$(scan_inline_value_module_directory \"$module\" \"$TARGET_ABS\")\n results=$(printf '%s\\n%s\\n%s' \"$r1\" \"$r2\" \"$r3\" | grep -v \"^$\" | sort -u || true)\n fi\n\n if [ -n \"$results\" ]; then\n # Store unique occurrences\n if [ -z \"${FOUND_MODULES[$module]}\" ]; then\n FOUND_MODULES[$module]=\"$fqcn|$collection\"\n NON_FQCN_FOUND=$((NON_FQCN_FOUND + 1))\n echo -e \"${COLOR_YELLOW}[NON-FQCN]${COLOR_RESET} ${COLOR_CYAN}$module${COLOR_RESET} → ${COLOR_GREEN}$fqcn${COLOR_RESET}\"\n echo \"$results\" | head -5 | while read -r line; do\n echo \" $line\"\n done\n local count=$(echo \"$results\" | wc -l | tr -d ' ')\n if [ \"$count\" -gt 5 ]; then\n echo \" ... and $((count - 5)) more occurrences\"\n fi\n echo \"\"\n fi\n fi\n}\n\necho -e \"${COLOR_BLUE}Checking for non-FQCN module usage...${COLOR_RESET}\"\necho \"\"\n\n# Check ansible.builtin modules\necho -e \"${COLOR_BLUE}Checking ansible.builtin modules:${COLOR_RESET}\"\necho \"\"\nfor module in \"${BUILTIN_MODULES[@]}\"; do\n check_module \"$module\" \"ansible.builtin.${module}\" \"ansible.builtin\"\ndone\n\n# Check community.general modules\necho -e \"${COLOR_BLUE}Checking community.general modules:${COLOR_RESET}\"\necho \"\"\nfor module in \"${COMMUNITY_GENERAL_MODULES[@]}\"; do\n check_module \"$module\" \"community.general.${module}\" \"community.general\"\ndone\n\n# Check ansible.posix modules\necho -e \"${COLOR_BLUE}Checking ansible.posix modules:${COLOR_RESET}\"\necho \"\"\nfor module in \"${ANSIBLE_POSIX_MODULES[@]}\"; do\n check_module \"$module\" \"ansible.posix.${module}\" \"ansible.posix\"\ndone\n\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}FQCN Check Summary${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\n\nif [ $NON_FQCN_FOUND -eq 0 ]; then\n echo -e \"${COLOR_GREEN}✓ All modules use FQCN format${COLOR_RESET}\"\n echo \"\"\n echo \"Great! Your Ansible code follows the recommended practice of using\"\n echo \"Fully Qualified Collection Names for all modules.\"\n exit 0\nelse\n echo -e \"${COLOR_YELLOW}⚠ Found $NON_FQCN_FOUND module(s) using short names instead of FQCN${COLOR_RESET}\"\n echo \"\"\n echo \"Migration Summary:\"\n echo \"====================\"\n for module in \"${!FOUND_MODULES[@]}\"; do\n IFS='|' read -r fqcn collection \u003c\u003c\u003c \"${FOUND_MODULES[$module]}\"\n echo -e \" ${COLOR_CYAN}$module${COLOR_RESET} → ${COLOR_GREEN}$fqcn${COLOR_RESET}\"\n done\n echo \"\"\n echo \"Why migrate to FQCN?\"\n echo \" 1. Clarity: Explicitly shows which collection provides the module\"\n echo \" 2. Conflict Prevention: Avoids naming conflicts between collections\"\n echo \" 3. Future-Proofing: Prevents breakage when modules move between collections\"\n echo \" 4. Best Practice: Recommended by Ansible for all new playbooks\"\n echo \"\"\n echo \"Required Collections:\"\n echo \" If using community.general modules:\"\n echo \" ansible-galaxy collection install community.general\"\n echo \" If using ansible.posix modules:\"\n echo \" ansible-galaxy collection install ansible.posix\"\n echo \"\"\n echo \"For detailed migration guidance, see:\"\n echo \" $SKILL_DIR/references/module_alternatives.md\"\n echo \"\"\n echo \"Note: This is a recommendation, not an error. Short names still work\"\n echo \"but may cause issues in future Ansible versions.\"\n exit 0\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":10409,"content_sha256":"eeff7130bcff44ca7c8b90a5f217df1e777bbd9ccf24b8dc09f0fb1224ffeaea"},{"filename":"scripts/extract_ansible_info_wrapper.sh","content":"#!/bin/bash\n# Wrapper script for extract_ansible_info.py that handles PyYAML dependency\n# Creates a temporary venv if PyYAML is not available\n\nset -e\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nPYTHON_SCRIPT=\"$SCRIPT_DIR/extract_ansible_info.py\"\n\n# Check if we have arguments\nif [ $# -lt 1 ]; then\n echo \"Usage: extract_ansible_info_wrapper.sh \u003cplaybook-or-role-path>\" >&2\n exit 1\nfi\n\nTARGET_PATH=\"$1\"\n\n# Try to run with system Python first\nif python3 -c \"import yaml\" 2>/dev/null; then\n # PyYAML is available, run directly\n python3 \"$PYTHON_SCRIPT\" \"$TARGET_PATH\"\n exit $?\nfi\n\n# PyYAML not available, create temporary venv\nTEMP_VENV=$(mktemp -d -t ansible-validator.XXXXXX)\ntrap \"rm -rf $TEMP_VENV\" EXIT\n\necho \"PyYAML not found in system Python. Creating temporary environment...\" >&2\n\n# Create venv and install PyYAML\npython3 -m venv \"$TEMP_VENV\" >&2\nsource \"$TEMP_VENV/bin/activate\" >&2\npip install --quiet pyyaml >&2\n\n# Run the script\npython3 \"$PYTHON_SCRIPT\" \"$TARGET_PATH\"\n\n# Cleanup happens automatically via trap\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1052,"content_sha256":"d9f91165edf12415db3333fe3a8e47d6bd2835de862f3c92ed1127e5091a56ca"},{"filename":"scripts/extract_ansible_info.py","content":"#!/usr/bin/env python3\n\"\"\"\nExtract Ansible module and collection information from playbooks and roles.\nOutputs JSON with detected modules, collections, and version constraints.\n\"\"\"\n\nimport sys\nimport json\nimport os\nimport re\nfrom pathlib import Path\nfrom typing import Dict, List, Set, Any\n\ntry:\n import yaml\nexcept ImportError:\n print(json.dumps({\n \"error\": \"PyYAML not installed\",\n \"message\": \"Please install PyYAML: pip install pyyaml\"\n }), file=sys.stderr)\n sys.exit(1)\n\n\nclass AnsibleInfoExtractor:\n \"\"\"Extract module and collection information from Ansible files\"\"\"\n\n # Common Ansible builtin modules (ansible.builtin collection)\n BUILTIN_MODULES = {\n 'debug', 'set_fact', 'assert', 'fail', 'include', 'include_tasks',\n 'include_vars', 'import_tasks', 'import_playbook', 'add_host',\n 'group_by', 'pause', 'wait_for', 'wait_for_connection', 'meta',\n 'command', 'shell', 'script', 'raw', 'copy', 'file', 'template',\n 'lineinfile', 'blockinfile', 'replace', 'fetch', 'stat', 'slurp',\n 'get_url', 'uri', 'apt', 'yum', 'dnf', 'package', 'service',\n 'systemd', 'user', 'group', 'authorized_key', 'cron', 'git', 'pip',\n 'setup', 'gather_facts', 'ping', 'reboot', 'include_role',\n 'import_role', 'hostname', 'sysctl', 'mount', 'unarchive', 'archive',\n 'find', 'known_hosts', 'iptables', 'firewalld', 'selinux',\n 'seboolean', 'at', 'acl', 'synchronize',\n }\n\n # Keys that indicate a top-level playbook list (not a plain task file).\n PLAYBOOK_KEYS = {\n 'hosts', 'tasks', 'pre_tasks', 'post_tasks', 'roles', 'handlers',\n 'import_playbook',\n }\n\n # Play-level keys that should never be treated as task modules.\n PLAY_ONLY_KEYS = {\n 'hosts', 'tasks', 'pre_tasks', 'post_tasks', 'roles', 'handlers',\n 'gather_facts', 'vars_files', 'vars_prompt', 'strategy', 'serial',\n 'max_fail_percentage', 'any_errors_fatal', 'import_playbook',\n }\n\n def __init__(self, path: str):\n self.path = Path(path)\n self.modules: Set[str] = set()\n self.collections: Set[str] = set()\n self.collection_versions: Dict[str, str] = {}\n self.errors: List[str] = []\n\n def extract(self) -> Dict[str, Any]:\n \"\"\"Extract information from Ansible files\"\"\"\n if self.path.is_file():\n self._process_file(self.path)\n elif self.path.is_dir():\n self._process_directory(self.path)\n else:\n self.errors.append(f\"Path not found: {self.path}\")\n\n return self._build_result()\n\n def _process_directory(self, directory: Path):\n \"\"\"Process all YAML files in directory\"\"\"\n # Check for requirements.yml first\n req_file = directory / 'requirements.yml'\n if req_file.exists():\n self._process_requirements(req_file)\n\n # Also check collections/requirements.yml (common location)\n coll_req = directory / 'collections' / 'requirements.yml'\n if coll_req.exists():\n self._process_requirements(coll_req)\n\n # Check for roles directory (Ansible role structure)\n roles_dir = directory / 'roles'\n if roles_dir.exists():\n for role_dir in roles_dir.iterdir():\n if role_dir.is_dir():\n self._process_role(role_dir)\n\n # Process all YAML files\n for ext in ['*.yml', '*.yaml']:\n for yaml_file in directory.rglob(ext):\n # Skip certain directories\n if any(skip in yaml_file.parts for skip in ['.git', 'venv', 'node_modules']):\n continue\n self._process_file(yaml_file)\n\n def _process_role(self, role_dir: Path):\n \"\"\"Process an Ansible role directory\"\"\"\n # Check for meta/main.yml for dependencies\n meta_file = role_dir / 'meta' / 'main.yml'\n if meta_file.exists():\n self._process_file(meta_file)\n\n # Process tasks\n tasks_dir = role_dir / 'tasks'\n if tasks_dir.exists():\n for task_file in tasks_dir.glob('*.yml'):\n self._process_file(task_file)\n\n # Process handlers\n handlers_dir = role_dir / 'handlers'\n if handlers_dir.exists():\n for handler_file in handlers_dir.glob('*.yml'):\n self._process_file(handler_file)\n\n def _process_file(self, file_path: Path):\n \"\"\"Process a single YAML file\"\"\"\n try:\n with open(file_path, 'r', encoding='utf-8') as f:\n content = yaml.safe_load(f)\n\n if not content:\n return\n\n if isinstance(content, list):\n # Distinguish a playbook from a plain task file by scanning\n # all list entries, not only the first one.\n if self._looks_like_playbook(content):\n for play in content:\n if not isinstance(play, dict):\n continue\n # Top-level import_playbook entries are playbook\n # includes, not task module invocations.\n if 'import_playbook' in play:\n continue\n self._extract_from_play(play)\n else:\n # It's a task file (tasks/main.yml, handlers/main.yml, etc.)\n self._extract_from_task_list(content)\n\n # Check if it's a task file expressed as a dict\n elif isinstance(content, dict):\n self._extract_from_tasks(content)\n\n except yaml.YAMLError as e:\n self.errors.append(f\"YAML error in {file_path}: {str(e)}\")\n except Exception as e:\n self.errors.append(f\"Error processing {file_path}: {str(e)}\")\n\n def _looks_like_playbook(self, items: List[Any]) -> bool:\n \"\"\"Detect whether a top-level YAML list is a playbook.\"\"\"\n for item in items:\n if not isinstance(item, dict):\n continue\n if any(key in item for key in self.PLAYBOOK_KEYS):\n return True\n return False\n\n def _process_requirements(self, req_file: Path):\n \"\"\"Process requirements.yml for collection versions\"\"\"\n try:\n with open(req_file, 'r', encoding='utf-8') as f:\n content = yaml.safe_load(f)\n\n if not content:\n return\n\n # Parse collections from requirements\n if isinstance(content, dict) and 'collections' in content:\n for collection in content['collections']:\n if isinstance(collection, dict):\n name = collection.get('name', '')\n version = collection.get('version', 'latest')\n if name:\n self.collections.add(name)\n self.collection_versions[name] = version\n elif isinstance(collection, str):\n self.collections.add(collection)\n self.collection_versions[collection] = 'latest'\n\n except Exception as e:\n self.errors.append(f\"Error processing requirements {req_file}: {str(e)}\")\n\n def _extract_from_play(self, play: Dict):\n \"\"\"Extract modules from a play\"\"\"\n # Process pre_tasks\n if 'pre_tasks' in play:\n self._extract_from_task_list(play['pre_tasks'])\n\n # Process tasks\n if 'tasks' in play:\n self._extract_from_task_list(play['tasks'])\n\n # Process post_tasks\n if 'post_tasks' in play:\n self._extract_from_task_list(play['post_tasks'])\n\n # Process handlers\n if 'handlers' in play:\n self._extract_from_task_list(play['handlers'])\n\n # Process roles\n if 'roles' in play:\n self._extract_from_roles(play['roles'])\n\n def _extract_from_tasks(self, content: Dict):\n \"\"\"Extract from task file content\"\"\"\n # If it's a list of tasks\n if isinstance(content, list):\n self._extract_from_task_list(content)\n # If it has a tasks key\n elif 'tasks' in content:\n self._extract_from_task_list(content['tasks'])\n\n def _extract_from_task_list(self, tasks: List):\n \"\"\"Extract modules from a list of tasks\"\"\"\n if not isinstance(tasks, list):\n return\n\n for task in tasks:\n if not isinstance(task, dict):\n continue\n\n # Extract module name from task\n for key in task.keys():\n # Skip Ansible keywords\n if key in ['name', 'when', 'with_items', 'loop', 'register',\n 'become', 'become_user', 'notify', 'tags', 'vars',\n 'changed_when', 'failed_when', 'ignore_errors',\n 'block', 'rescue', 'always', 'delegate_to']:\n continue\n if key in self.PLAY_ONLY_KEYS:\n continue\n\n # Check for blocks\n if key == 'block' and isinstance(task['block'], list):\n self._extract_from_task_list(task['block'])\n if key == 'rescue' and isinstance(task['rescue'], list):\n self._extract_from_task_list(task['rescue'])\n if key == 'always' and isinstance(task['always'], list):\n self._extract_from_task_list(task['always'])\n\n # Extract module name\n if '.' in key:\n # It's a FQCN (Fully Qualified Collection Name)\n parts = key.split('.')\n if len(parts) >= 3:\n collection = f\"{parts[0]}.{parts[1]}\"\n self.collections.add(collection)\n self.modules.add(key)\n elif len(parts) == 2:\n # Could be collection.module (two-part FQCN)\n self.modules.add(key)\n else:\n # It's a short module name — track all of them, builtin or not\n if not key.startswith('_'):\n self.modules.add(key)\n\n def _extract_from_roles(self, roles):\n \"\"\"Extract information from roles\"\"\"\n if not isinstance(roles, list):\n return\n\n for role in roles:\n if isinstance(role, dict):\n # Role with parameters\n role_name = role.get('role', role.get('name', ''))\n if '.' in role_name:\n # It's a collection role\n parts = role_name.split('.')\n if len(parts) >= 2:\n collection = f\"{parts[0]}.{parts[1]}\"\n self.collections.add(collection)\n\n def _is_builtin(self, module_name: str) -> bool:\n \"\"\"Return True for both short-name and FQCN ansible.builtin modules.\"\"\"\n if module_name in self.BUILTIN_MODULES:\n return True\n # ansible.builtin.apt / ansible.legacy.apt\n if '.' in module_name:\n prefix = '.'.join(module_name.split('.')[:2])\n short = module_name.split('.')[-1]\n if prefix in ('ansible.builtin', 'ansible.legacy'):\n return short in self.BUILTIN_MODULES\n return False\n\n def _build_result(self) -> Dict[str, Any]:\n \"\"\"Build the result dictionary\"\"\"\n return {\n \"modules\": sorted(list(self.modules)),\n \"collections\": sorted(list(self.collections)),\n \"collection_versions\": self.collection_versions,\n \"builtin_modules\": sorted([m for m in self.modules if self._is_builtin(m)]),\n \"custom_modules\": sorted([m for m in self.modules if not self._is_builtin(m)]),\n \"errors\": self.errors\n }\n\n\ndef main():\n if len(sys.argv) \u003c 2:\n print(json.dumps({\n \"error\": \"No path provided\",\n \"usage\": \"python extract_ansible_info.py \u003cpath-to-playbook-or-role>\"\n }))\n sys.exit(1)\n\n path = sys.argv[1]\n extractor = AnsibleInfoExtractor(path)\n result = extractor.extract()\n\n print(json.dumps(result, indent=2))\n\n # Exit with error code if there were errors\n if result['errors']:\n sys.exit(1)\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":12370,"content_sha256":"384a8de350c6227b9f94aef8625b3feafa34588b38673c0d9c0c18148a2e71e1"},{"filename":"scripts/scan_secrets.sh","content":"#!/usr/bin/env bash\n\n# Ansible Secret Scanner\n# Scans Ansible files for hardcoded secrets that Checkov may miss\n# This complements Checkov security scanning with grep-based detection\n\nset -e\n\nTARGET=\"$1\"\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nSKILL_DIR=\"$(dirname \"$SCRIPT_DIR\")\"\n\nCOLOR_GREEN='\\033[0;32m'\nCOLOR_YELLOW='\\033[1;33m'\nCOLOR_RED='\\033[0;31m'\nCOLOR_BLUE='\\033[0;34m'\nCOLOR_RESET='\\033[0m'\n\n# Usage check\nif [ -z \"$TARGET\" ]; then\n echo \"Usage: $0 \u003cplaybook.yml|role-directory|directory>\"\n echo \"\"\n echo \"Scans Ansible files for hardcoded secrets including:\"\n echo \" - Passwords\"\n echo \" - API keys\"\n echo \" - Tokens\"\n echo \" - Private keys\"\n echo \" - AWS credentials\"\n echo \" - Database connection strings\"\n exit 1\nfi\n\nif [ ! -f \"$TARGET\" ] && [ ! -d \"$TARGET\" ]; then\n echo -e \"${COLOR_RED}Error: Target not found: $TARGET${COLOR_RESET}\"\n exit 1\nfi\n\n# Get absolute path\nif [ -f \"$TARGET\" ]; then\n TARGET_ABS=$(cd \"$(dirname \"$TARGET\")\" && pwd)/$(basename \"$TARGET\")\n SCAN_TYPE=\"file\"\nelse\n TARGET_ABS=$(cd \"$TARGET\" && pwd)\n SCAN_TYPE=\"directory\"\nfi\n\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}Ansible Hardcoded Secret Scanner${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho \"\"\necho \"Scanning: $TARGET_ABS\"\necho \"\"\n\nSECRETS_FOUND=0\nWARNINGS_FOUND=0\n\n# Function to scan for patterns\nscan_pattern() {\n local pattern=\"$1\"\n local description=\"$2\"\n local severity=\"$3\" # ERROR or WARNING\n local results=\"\"\n\n if [ \"$SCAN_TYPE\" = \"file\" ]; then\n results=$(grep -n -i \"$pattern\" \"$TARGET_ABS\" 2>/dev/null || true)\n else\n results=$(grep -r -n -i \"$pattern\" \"$TARGET_ABS\" --include=\"*.yml\" --include=\"*.yaml\" 2>/dev/null | grep -v \".git/\" || true)\n fi\n\n if [ -n \"$results\" ]; then\n if [ \"$severity\" = \"ERROR\" ]; then\n echo -e \"${COLOR_RED}[SECRET DETECTED]${COLOR_RESET} $description\"\n SECRETS_FOUND=$((SECRETS_FOUND + 1))\n else\n echo -e \"${COLOR_YELLOW}[WARNING]${COLOR_RESET} $description\"\n WARNINGS_FOUND=$((WARNINGS_FOUND + 1))\n fi\n echo \"$results\" | while read -r line; do\n echo \" $line\"\n done\n echo \"\"\n fi\n}\n\n# Function to check for non-vaulted password values\ncheck_password_values() {\n local results=\"\"\n\n if [ \"$SCAN_TYPE\" = \"file\" ]; then\n # Look for password: followed by a non-variable value (not {{ }})\n results=$(grep -n -E \"password:\\s*['\\\"]?[^{'\\\"][^'\\\"]*['\\\"]?\\s*$\" \"$TARGET_ABS\" 2>/dev/null || true)\n results+=$(grep -n -E \"password:\\s*['\\\"][^{][^'\\\"]+['\\\"]\" \"$TARGET_ABS\" 2>/dev/null || true)\n else\n results=$(grep -r -n -E \"password:\\s*['\\\"]?[^{'\\\"][^'\\\"]*['\\\"]?\\s*$\" \"$TARGET_ABS\" --include=\"*.yml\" --include=\"*.yaml\" 2>/dev/null | grep -v \".git/\" || true)\n results+=$(grep -r -n -E \"password:\\s*['\\\"][^{][^'\\\"]+['\\\"]\" \"$TARGET_ABS\" --include=\"*.yml\" --include=\"*.yaml\" 2>/dev/null | grep -v \".git/\" | grep -v \"password_hash\" || true)\n fi\n\n # Filter out false positives (empty values, variable references, vault references)\n if [ -n \"$results\" ]; then\n filtered=$(echo \"$results\" | grep -v \"password:\\s*$\" | grep -v \"password:\\s*\\\"\\s*\\\"\" | grep -v \"password:\\s*'\\s*'\" | grep -v \"{{\" | grep -v \"vault_\" | grep -v \"!vault\" || true)\n if [ -n \"$filtered\" ]; then\n echo -e \"${COLOR_RED}[SECRET DETECTED]${COLOR_RESET} Hardcoded password value found\"\n SECRETS_FOUND=$((SECRETS_FOUND + 1))\n echo \"$filtered\" | while read -r line; do\n echo \" $line\"\n done\n echo \"\"\n fi\n fi\n}\n\necho -e \"${COLOR_BLUE}Scanning for hardcoded secrets...${COLOR_RESET}\"\necho \"\"\n\n# High severity - likely secrets\nscan_pattern \"password:\\s*['\\\"]\" \"Potential hardcoded password\" \"ERROR\"\nscan_pattern \"api_key:\\s*['\\\"]\" \"Potential hardcoded API key\" \"ERROR\"\nscan_pattern \"apikey:\\s*['\\\"]\" \"Potential hardcoded API key\" \"ERROR\"\nscan_pattern \"secret:\\s*['\\\"]\" \"Potential hardcoded secret\" \"ERROR\"\nscan_pattern \"secret_key:\\s*['\\\"]\" \"Potential hardcoded secret key\" \"ERROR\"\nscan_pattern \"private_key:\\s*['\\\"]\" \"Potential hardcoded private key\" \"ERROR\"\nscan_pattern \"token:\\s*['\\\"]\" \"Potential hardcoded token\" \"ERROR\"\nscan_pattern \"auth_token:\\s*['\\\"]\" \"Potential hardcoded auth token\" \"ERROR\"\nscan_pattern \"access_token:\\s*['\\\"]\" \"Potential hardcoded access token\" \"ERROR\"\n\n# AWS credentials\nscan_pattern \"aws_access_key_id:\\s*['\\\"]?[A-Z0-9]\" \"Potential hardcoded AWS Access Key\" \"ERROR\"\nscan_pattern \"aws_secret_access_key:\\s*['\\\"]\" \"Potential hardcoded AWS Secret Key\" \"ERROR\"\nscan_pattern \"AKIA[A-Z0-9]{16}\" \"AWS Access Key ID pattern detected\" \"ERROR\"\n\n# Database connection strings\nscan_pattern \"mysql://.*:.*@\" \"Potential database connection string with credentials\" \"ERROR\"\nscan_pattern \"postgres://.*:.*@\" \"Potential database connection string with credentials\" \"ERROR\"\nscan_pattern \"mongodb://.*:.*@\" \"Potential database connection string with credentials\" \"ERROR\"\n\n# Private key content\nscan_pattern \"BEGIN RSA PRIVATE KEY\" \"Private key content detected\" \"ERROR\"\nscan_pattern \"BEGIN OPENSSH PRIVATE KEY\" \"OpenSSH private key content detected\" \"ERROR\"\nscan_pattern \"BEGIN EC PRIVATE KEY\" \"EC private key content detected\" \"ERROR\"\nscan_pattern \"BEGIN DSA PRIVATE KEY\" \"DSA private key content detected\" \"ERROR\"\n\n# Medium severity - may be secrets\nscan_pattern \"credentials:\\s*['\\\"]\" \"Potential hardcoded credentials\" \"WARNING\"\nscan_pattern \"db_password:\\s*['\\\"]\" \"Potential hardcoded database password\" \"WARNING\"\nscan_pattern \"database_password:\\s*['\\\"]\" \"Potential hardcoded database password\" \"WARNING\"\nscan_pattern \"admin_password:\\s*['\\\"]\" \"Potential hardcoded admin password\" \"WARNING\"\nscan_pattern \"root_password:\\s*['\\\"]\" \"Potential hardcoded root password\" \"WARNING\"\nscan_pattern \"ssh_pass:\\s*['\\\"]\" \"Potential hardcoded SSH password\" \"WARNING\"\n\n# Check for password values that are not variable references\ncheck_password_values\n\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}Secret Scan Summary${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\n\nif [ $SECRETS_FOUND -eq 0 ] && [ $WARNINGS_FOUND -eq 0 ]; then\n echo -e \"${COLOR_GREEN}✓ No hardcoded secrets detected${COLOR_RESET}\"\n echo \"\"\n echo \"Note: This scan uses pattern matching and may not catch all secrets.\"\n echo \"Always use Ansible Vault for sensitive data.\"\n exit 0\nelif [ $SECRETS_FOUND -eq 0 ]; then\n echo -e \"${COLOR_YELLOW}⚠ Found $WARNINGS_FOUND potential secret(s) requiring review${COLOR_RESET}\"\n echo \"\"\n echo \"Recommendations:\"\n echo \" 1. Review the warnings above to determine if they contain actual secrets\"\n echo \" 2. Use Ansible Vault to encrypt sensitive values\"\n echo \" 3. Use environment variables or external secret management\"\n echo \"\"\n echo \"For secure secret management, see:\"\n echo \" $SKILL_DIR/references/security_checklist.md\"\n exit 0\nelse\n echo -e \"${COLOR_RED}✗ Found $SECRETS_FOUND secret(s) and $WARNINGS_FOUND warning(s)${COLOR_RESET}\"\n echo \"\"\n echo \"CRITICAL: Hardcoded secrets were detected!\"\n echo \"\"\n echo \"Remediation steps:\"\n echo \" 1. Never commit hardcoded secrets to version control\"\n echo \" 2. Use Ansible Vault to encrypt sensitive data:\"\n echo \" ansible-vault encrypt_string 'secret_value' --name 'variable_name'\"\n echo \" 3. Use lookup plugins for external secrets:\"\n echo \" password: \\\"{{ lookup('env', 'DB_PASSWORD') }}\\\"\"\n echo \" 4. Use HashiCorp Vault or similar for production:\"\n echo \" password: \\\"{{ lookup('hashi_vault', 'secret=secret/data/db:password') }}\\\"\"\n echo \"\"\n echo \"For detailed guidance, see:\"\n echo \" $SKILL_DIR/references/security_checklist.md\"\n exit 1\nfi","content_type":"application/x-sh; charset=utf-8","language":"bash","size":7967,"content_sha256":"9557d9f37c5d029b6f80eca46f88ca8c57324984785e2811d63ea42e47f526d8"},{"filename":"scripts/setup_tools.sh","content":"#!/usr/bin/env bash\n\n# Setup and validation tool for Ansible Validator\n# Checks for required tools and provides installation instructions\n\nset -e\n\nCOLOR_GREEN='\\033[0;32m'\nCOLOR_YELLOW='\\033[1;33m'\nCOLOR_RED='\\033[0;31m'\nCOLOR_RESET='\\033[0m'\n\necho \"Checking Ansible validation tools...\"\necho \"\"\n\nMISSING_TOOLS=()\nINSTALLED_TOOLS=()\nRUNTIME_WARNINGS=()\nDOCKER_READY=0\nPODMAN_READY=0\nMOLECULE_FOUND=0\n\n# Function to check if command exists\ncommand_exists() {\n command -v \"$1\" >/dev/null 2>&1\n}\n\n# Function to get version\nget_version() {\n $1 --version 2>/dev/null | head -n 1\n}\n\n# Check ansible\necho -n \"Checking ansible... \"\nif command_exists ansible; then\n VERSION=$(ansible --version | head -n 1)\n echo -e \"${COLOR_GREEN}✓ Found${COLOR_RESET} - $VERSION\"\n INSTALLED_TOOLS+=(\"ansible\")\nelse\n echo -e \"${COLOR_RED}✗ Not found${COLOR_RESET}\"\n MISSING_TOOLS+=(\"ansible\")\nfi\n\n# Check ansible-playbook\necho -n \"Checking ansible-playbook... \"\nif command_exists ansible-playbook; then\n VERSION=$(ansible-playbook --version | head -n 1)\n echo -e \"${COLOR_GREEN}✓ Found${COLOR_RESET} - $VERSION\"\n INSTALLED_TOOLS+=(\"ansible-playbook\")\nelse\n echo -e \"${COLOR_RED}✗ Not found${COLOR_RESET}\"\n MISSING_TOOLS+=(\"ansible-playbook\")\nfi\n\n# Check ansible-lint\necho -n \"Checking ansible-lint... \"\nif command_exists ansible-lint; then\n VERSION=$(ansible-lint --version)\n echo -e \"${COLOR_GREEN}✓ Found${COLOR_RESET} - $VERSION\"\n INSTALLED_TOOLS+=(\"ansible-lint\")\nelse\n echo -e \"${COLOR_RED}✗ Not found${COLOR_RESET}\"\n MISSING_TOOLS+=(\"ansible-lint\")\nfi\n\n# Check yamllint\necho -n \"Checking yamllint... \"\nif command_exists yamllint; then\n VERSION=$(yamllint --version)\n echo -e \"${COLOR_GREEN}✓ Found${COLOR_RESET} - $VERSION\"\n INSTALLED_TOOLS+=(\"yamllint\")\nelse\n echo -e \"${COLOR_RED}✗ Not found${COLOR_RESET}\"\n MISSING_TOOLS+=(\"yamllint\")\nfi\n\n# Check checkov (recommended for security scanning)\necho -n \"Checking checkov (recommended)... \"\nif command_exists checkov; then\n VERSION=$(checkov --version 2>/dev/null | head -n 1)\n echo -e \"${COLOR_GREEN}✓ Found${COLOR_RESET} - $VERSION\"\n INSTALLED_TOOLS+=(\"checkov\")\nelse\n echo -e \"${COLOR_YELLOW}⚠ Not found${COLOR_RESET} (recommended for security scanning)\"\nfi\n\n# Check molecule (optional but recommended)\necho -n \"Checking molecule (optional)... \"\nif command_exists molecule; then\n VERSION=$(molecule --version | head -n 1)\n echo -e \"${COLOR_GREEN}✓ Found${COLOR_RESET} - $VERSION\"\n INSTALLED_TOOLS+=(\"molecule\")\n MOLECULE_FOUND=1\nelse\n echo -e \"${COLOR_YELLOW}⚠ Not found${COLOR_RESET} (optional)\"\nfi\n\n# Check Docker runtime (optional, required for common Molecule drivers)\necho -n \"Checking docker runtime (for molecule docker driver)... \"\nif command_exists docker; then\n VERSION=$(docker --version 2>/dev/null | head -n 1)\n if docker info >/dev/null 2>&1; then\n echo -e \"${COLOR_GREEN}✓ Ready${COLOR_RESET} - $VERSION\"\n INSTALLED_TOOLS+=(\"docker\")\n DOCKER_READY=1\n else\n echo -e \"${COLOR_YELLOW}⚠ Found but daemon unavailable${COLOR_RESET} - $VERSION\"\n RUNTIME_WARNINGS+=(\"docker is installed but daemon/socket is not reachable\")\n fi\nelse\n echo -e \"${COLOR_YELLOW}⚠ Not found${COLOR_RESET} (optional, needed for docker-based Molecule)\"\nfi\n\n# Check Podman runtime (optional alternative to Docker for Molecule)\necho -n \"Checking podman runtime (for molecule podman driver)... \"\nif command_exists podman; then\n VERSION=$(podman --version 2>/dev/null | head -n 1)\n if podman info >/dev/null 2>&1; then\n echo -e \"${COLOR_GREEN}✓ Ready${COLOR_RESET} - $VERSION\"\n INSTALLED_TOOLS+=(\"podman\")\n PODMAN_READY=1\n else\n echo -e \"${COLOR_YELLOW}⚠ Found but runtime unavailable${COLOR_RESET} - $VERSION\"\n RUNTIME_WARNINGS+=(\"podman is installed but runtime info is unavailable\")\n fi\nelse\n echo -e \"${COLOR_YELLOW}⚠ Not found${COLOR_RESET} (optional, needed for podman-based Molecule)\"\nfi\n\n# Check ansible-galaxy\necho -n \"Checking ansible-galaxy... \"\nif command_exists ansible-galaxy; then\n VERSION=$(ansible-galaxy --version | head -n 1)\n echo -e \"${COLOR_GREEN}✓ Found${COLOR_RESET} - $VERSION\"\n INSTALLED_TOOLS+=(\"ansible-galaxy\")\nelse\n echo -e \"${COLOR_RED}✗ Not found${COLOR_RESET}\"\n MISSING_TOOLS+=(\"ansible-galaxy\")\nfi\n\n# Check python3\necho -n \"Checking python3... \"\nif command_exists python3; then\n VERSION=$(python3 --version)\n echo -e \"${COLOR_GREEN}✓ Found${COLOR_RESET} - $VERSION\"\n INSTALLED_TOOLS+=(\"python3\")\nelse\n echo -e \"${COLOR_RED}✗ Not found${COLOR_RESET}\"\n MISSING_TOOLS+=(\"python3\")\nfi\n\necho \"\"\necho \"========================================\"\n\nif [ $MOLECULE_FOUND -eq 1 ] && [ $DOCKER_READY -eq 0 ] && [ $PODMAN_READY -eq 0 ]; then\n RUNTIME_WARNINGS+=(\"molecule is installed but no ready container runtime (docker/podman); molecule tests may be BLOCKED\")\nfi\n\n# Summary\nif [ ${#MISSING_TOOLS[@]} -eq 0 ]; then\n echo -e \"${COLOR_GREEN}All required tools are installed!${COLOR_RESET}\"\n if [ ${#RUNTIME_WARNINGS[@]} -gt 0 ]; then\n echo \"\"\n echo -e \"${COLOR_YELLOW}Runtime warnings:${COLOR_RESET}\"\n for warning in \"${RUNTIME_WARNINGS[@]}\"; do\n echo \" - $warning\"\n done\n echo \"\"\n echo \"Validation can proceed. Molecule stages may be marked BLOCKED until runtime is ready.\"\n fi\n exit 0\nelse\n echo -e \"${COLOR_YELLOW}Missing tools detected${COLOR_RESET}\"\n echo \"\"\n echo \"Installation instructions:\"\n echo \"\"\n\n # Check OS and provide appropriate installation commands\n if [[ \"$OSTYPE\" == \"darwin\"* ]]; then\n # macOS\n echo \"macOS detected - Install with Homebrew or pip:\"\n echo \"\"\n echo \" # Install with pip (recommended):\"\n echo \" pip3 install --upgrade ansible ansible-lint yamllint checkov\"\n echo \"\"\n echo \" # Or with Homebrew:\"\n echo \" brew install ansible ansible-lint yamllint\"\n echo \" pip3 install checkov # checkov not available via brew\"\n echo \"\"\n echo \" # Optional: Install molecule for role testing\"\n echo \" pip3 install molecule molecule-docker\"\n\n elif [[ \"$OSTYPE\" == \"linux-gnu\"* ]]; then\n # Linux\n if command_exists apt-get; then\n # Debian/Ubuntu\n echo \"Debian/Ubuntu detected - Install with apt and pip:\"\n echo \"\"\n echo \" sudo apt-get update\"\n echo \" sudo apt-get install -y python3-pip\"\n echo \" pip3 install --upgrade ansible ansible-lint yamllint checkov\"\n echo \"\"\n echo \" # Optional: Install molecule for role testing\"\n echo \" pip3 install molecule molecule-docker\"\n\n elif command_exists yum; then\n # RHEL/CentOS\n echo \"RHEL/CentOS detected - Install with yum and pip:\"\n echo \"\"\n echo \" sudo yum install -y python3-pip\"\n echo \" pip3 install --upgrade ansible ansible-lint yamllint checkov\"\n echo \"\"\n echo \" # Optional: Install molecule for role testing\"\n echo \" pip3 install molecule molecule-docker\"\n\n elif command_exists dnf; then\n # Fedora\n echo \"Fedora detected - Install with dnf and pip:\"\n echo \"\"\n echo \" sudo dnf install -y python3-pip\"\n echo \" pip3 install --upgrade ansible ansible-lint yamllint checkov\"\n echo \"\"\n echo \" # Optional: Install molecule for role testing\"\n echo \" pip3 install molecule molecule-docker\"\n else\n echo \"Linux detected - Install with pip:\"\n echo \"\"\n echo \" pip3 install --upgrade ansible ansible-lint yamllint checkov\"\n echo \"\"\n echo \" # Optional: Install molecule for role testing\"\n echo \" pip3 install molecule molecule-docker\"\n fi\n else\n # Generic\n echo \"Install with pip:\"\n echo \"\"\n echo \" pip3 install --upgrade ansible ansible-lint yamllint checkov\"\n echo \"\"\n echo \" # Optional: Install molecule for role testing\"\n echo \" pip3 install molecule molecule-docker\"\n fi\n\n if [ ${#RUNTIME_WARNINGS[@]} -gt 0 ]; then\n echo \"\"\n echo -e \"${COLOR_YELLOW}Runtime warnings:${COLOR_RESET}\"\n for warning in \"${RUNTIME_WARNINGS[@]}\"; do\n echo \" - $warning\"\n done\n fi\n\n echo \"\"\n echo \"After installation, run this script again to verify.\"\n exit 1\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":8587,"content_sha256":"407442d12f5fc307a1ac1c7f894d86fe9bc9dc545edae883cbc163e8bd44f1b6"},{"filename":"scripts/test_role.sh","content":"#!/usr/bin/env bash\n\n# Ansible Role Testing Script with Molecule\n# Automatically installs molecule in temporary venv if not available\n\nset -e\n\nROLE_DIR=\"$1\"\nSCENARIO=\"${2:-default}\"\n\nCOLOR_GREEN='\\033[0;32m'\nCOLOR_YELLOW='\\033[1;33m'\nCOLOR_RED='\\033[0;31m'\nCOLOR_BLUE='\\033[0;34m'\nCOLOR_RESET='\\033[0m'\nBLOCKED_EXIT_CODE=2\n\n# Usage check\nif [ -z \"$ROLE_DIR\" ]; then\n echo \"Usage: $0 \u003crole-directory> [scenario]\"\n echo \"\"\n echo \"Arguments:\"\n echo \" role-directory Path to the Ansible role\"\n echo \" scenario Molecule scenario name (default: default)\"\n exit 1\nfi\n\nif [ ! -d \"$ROLE_DIR\" ]; then\n echo -e \"${COLOR_RED}Error: Role directory not found: $ROLE_DIR${COLOR_RESET}\"\n exit 1\nfi\n\n# Get absolute path to role\nROLE_ABS_PATH=$(cd \"$ROLE_DIR\" && pwd)\n\nreport_blocked() {\n local reason=\"$1\"\n echo \"\"\n echo -e \"${COLOR_YELLOW}Molecule Status: BLOCKED${COLOR_RESET}\"\n echo \"Reason: $reason\"\n echo \"Fallback Applied: Continue syntax/lint/check-mode/security validation without Molecule runtime tests.\"\n echo \"Next Action: Fix runtime/dependency and rerun: bash scripts/test_role.sh $ROLE_ABS_PATH $SCENARIO\"\n exit \"$BLOCKED_EXIT_CODE\"\n}\n\nMOLECULE_DRIVER=\"docker\"\nSCENARIO_CONFIG=\"$ROLE_ABS_PATH/molecule/$SCENARIO/molecule.yml\"\n\nif [ -f \"$SCENARIO_CONFIG\" ]; then\n DETECTED_DRIVER=$(awk '\n /^driver:/ { in_driver=1; next }\n in_driver && /^[^[:space:]]/ { in_driver=0 }\n in_driver && /^[[:space:]]+name:/ { print $2; exit }\n ' \"$SCENARIO_CONFIG\" | tr -d \"\\\"'\" | tr '[:upper:]' '[:lower:]')\n if [ -n \"$DETECTED_DRIVER\" ]; then\n MOLECULE_DRIVER=\"$DETECTED_DRIVER\"\n fi\nfi\n\nDRIVER_PLUGIN_HINT=\"molecule-docker\"\nif [ \"$MOLECULE_DRIVER\" = \"podman\" ]; then\n DRIVER_PLUGIN_HINT=\"molecule-podman\"\nfi\n\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}Ansible Role Testing with Molecule${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho \"\"\necho \"Role: $ROLE_ABS_PATH\"\necho \"Scenario: $SCENARIO\"\necho \"Detected Driver: $MOLECULE_DRIVER\"\necho \"\"\n\n# Check if molecule is configured\nif [ ! -d \"$ROLE_ABS_PATH/molecule/$SCENARIO\" ]; then\n echo -e \"${COLOR_YELLOW}⚠ Molecule scenario '$SCENARIO' not found${COLOR_RESET}\"\n echo \"\"\n echo \"Available scenarios:\"\n if [ -d \"$ROLE_ABS_PATH/molecule\" ]; then\n ls -1 \"$ROLE_ABS_PATH/molecule/\"\n else\n echo \" None - molecule not initialized\"\n fi\n echo \"\"\n echo \"Initialize molecule with:\"\n echo \" cd $ROLE_ABS_PATH\"\n echo \" molecule init scenario --driver-name docker\"\n exit 1\nfi\n\ncheck_runtime_ready() {\n case \"$MOLECULE_DRIVER\" in\n docker)\n if ! command -v docker >/dev/null 2>&1; then\n report_blocked \"Docker CLI not found for Molecule docker driver.\"\n fi\n if ! docker info >/dev/null 2>&1; then\n report_blocked \"Docker daemon is not reachable (docker info failed).\"\n fi\n ;;\n podman)\n if ! command -v podman >/dev/null 2>&1; then\n report_blocked \"Podman CLI not found for Molecule podman driver.\"\n fi\n if ! podman info >/dev/null 2>&1; then\n report_blocked \"Podman runtime is not reachable (podman info failed).\"\n fi\n ;;\n delegated|default)\n # No container runtime preflight required for delegated/default drivers.\n ;;\n *)\n echo -e \"${COLOR_YELLOW}⚠ Driver '$MOLECULE_DRIVER' detected - runtime preflight is best effort${COLOR_RESET}\"\n ;;\n esac\n}\n\ncheck_runtime_ready\n\n# Function to run molecule command\nrun_molecule() {\n if [ -n \"$TEMP_VENV\" ]; then\n # Using temporary venv\n \"$TEMP_VENV/bin/molecule\" \"$@\"\n else\n # Using system molecule\n molecule \"$@\"\n fi\n}\n\n# Check if molecule is available in system\nTEMP_VENV=\"\"\nCLEANUP_VENV=0\n\nif command -v molecule >/dev/null 2>&1; then\n echo -e \"${COLOR_GREEN}✓ Using system molecule${COLOR_RESET}\"\n echo \"\"\nelse\n echo -e \"${COLOR_YELLOW}⚠ Molecule not found in system${COLOR_RESET}\"\n echo \"Creating temporary environment with molecule...\"\n echo \"\"\n\n if ! command -v python3 >/dev/null 2>&1; then\n report_blocked \"python3 is required to bootstrap molecule in a temporary virtual environment.\"\n fi\n\n # Create temporary venv\n TEMP_VENV=$(mktemp -d -t ansible-molecule.XXXXXX)\n CLEANUP_VENV=1\n\n # Setup cleanup trap\n cleanup() {\n if [ $CLEANUP_VENV -eq 1 ] && [ -n \"$TEMP_VENV\" ]; then\n echo \"\"\n echo \"Cleaning up temporary environment...\"\n rm -rf \"$TEMP_VENV\"\n fi\n }\n trap cleanup EXIT INT TERM\n\n # Create venv and install molecule\n echo \"Installing molecule and dependencies (this may take a minute)...\"\n if ! python3 -m venv \"$TEMP_VENV\" >/dev/null 2>&1; then\n report_blocked \"Failed to create Python virtual environment for Molecule bootstrap.\"\n fi\n\n # Activate venv and install\n # shellcheck disable=SC1091\n source \"$TEMP_VENV/bin/activate\"\n\n DRIVER_PLUGIN=\"\"\n case \"$MOLECULE_DRIVER\" in\n docker)\n DRIVER_PLUGIN=\"molecule-docker\"\n ;;\n podman)\n DRIVER_PLUGIN=\"molecule-podman\"\n ;;\n esac\n\n if ! pip install --quiet --upgrade pip setuptools wheel; then\n report_blocked \"pip bootstrap failed while preparing Molecule temporary environment.\"\n fi\n\n if [ -n \"$DRIVER_PLUGIN\" ]; then\n if ! pip install --quiet molecule \"$DRIVER_PLUGIN\" ansible-core ansible-lint yamllint; then\n report_blocked \"Failed to install Molecule dependencies ($DRIVER_PLUGIN) in temporary environment.\"\n fi\n else\n if ! pip install --quiet molecule ansible-core ansible-lint yamllint; then\n report_blocked \"Failed to install Molecule dependencies in temporary environment.\"\n fi\n fi\n\n echo -e \"${COLOR_GREEN}✓ Temporary molecule environment ready${COLOR_RESET}\"\n echo \"\"\nfi\n\ncd \"$ROLE_ABS_PATH\"\n\n# List molecule scenarios\necho -e \"${COLOR_BLUE}Available Scenarios:${COLOR_RESET}\"\nif ! run_molecule list -s \"$SCENARIO\"; then\n echo -e \"${COLOR_RED}✗ Unable to list Molecule scenarios${COLOR_RESET}\"\n echo \"Run manually for details: molecule list -s $SCENARIO\"\n exit 1\nfi\necho \"\"\n\n# Run molecule test\necho -e \"${COLOR_BLUE}Running Molecule Test Sequence...${COLOR_RESET}\"\necho \"\"\n\n# Full test sequence with better error handling\nSTAGE_ERRORS=0\n\nrun_stage() {\n local stage=$1\n local description=$2\n\n echo -e \"${COLOR_BLUE}[$stage] $description${COLOR_RESET}\"\n echo \"-----------------------------------\"\n\n if run_molecule \"$stage\" -s \"$SCENARIO\"; then\n echo -e \"${COLOR_GREEN}✓ $description completed${COLOR_RESET}\"\n echo \"\"\n return 0\n else\n echo -e \"${COLOR_RED}✗ $description failed${COLOR_RESET}\"\n echo \"\"\n STAGE_ERRORS=$((STAGE_ERRORS + 1))\n return 1\n fi\n}\n\n# Run test stages\n# Note: molecule lint was removed in v5+, linting is now done separately\n# run_stage \"lint\" \"Lint Check\"\nrun_stage \"syntax\" \"Syntax Check\"\nrun_stage \"create\" \"Create Test Instances\"\nrun_stage \"converge\" \"Run Role (Converge)\"\n\n# Idempotence test (critical for Ansible roles)\necho -e \"${COLOR_BLUE}[Idempotence] Idempotence Test${COLOR_RESET}\"\necho \"-----------------------------------\"\nif run_molecule converge -s \"$SCENARIO\" 2>&1 | grep -q \"changed=0\"; then\n echo -e \"${COLOR_GREEN}✓ Idempotence test passed${COLOR_RESET}\"\nelse\n echo -e \"${COLOR_RED}✗ Idempotence test failed - role is not idempotent${COLOR_RESET}\"\n STAGE_ERRORS=$((STAGE_ERRORS + 1))\nfi\necho \"\"\n\n# Verify stage\nrun_stage \"verify\" \"Verification Tests\"\n\n# Cleanup\necho -e \"${COLOR_BLUE}[Cleanup] Destroying Test Instances${COLOR_RESET}\"\necho \"-----------------------------------\"\nrun_molecule destroy -s \"$SCENARIO\" || true\necho \"\"\n\n# Summary\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}Test Summary${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\n\nif [ $STAGE_ERRORS -eq 0 ]; then\n echo -e \"${COLOR_GREEN}✓ All tests passed successfully!${COLOR_RESET}\"\n echo \"\"\n echo \"The role is ready for use.\"\n echo \"\"\n if [ -n \"$TEMP_VENV\" ]; then\n echo \"Note: Molecule was installed in a temporary environment for this test.\"\n echo \"To install permanently: pip install molecule $DRIVER_PLUGIN_HINT\"\n fi\n exit 0\nelse\n echo -e \"${COLOR_RED}✗ Tests failed with $STAGE_ERRORS error(s)${COLOR_RESET}\"\n echo \"\"\n echo \"Debug with:\"\n echo \" cd $ROLE_ABS_PATH\"\n if [ -n \"$TEMP_VENV\" ]; then\n echo \" # Using temporary venv:\"\n echo \" source $TEMP_VENV/bin/activate\"\n echo \" molecule converge -s $SCENARIO # Run without cleanup\"\n echo \" molecule login -s $SCENARIO # SSH into test instance\"\n echo \" molecule verify -s $SCENARIO # Re-run verification\"\n echo \" molecule destroy -s $SCENARIO # Clean up when done\"\n echo \" deactivate\"\n echo \"\"\n echo \"Or install molecule permanently:\"\n echo \" pip install molecule $DRIVER_PLUGIN_HINT\"\n else\n echo \" molecule converge -s $SCENARIO # Run without cleanup\"\n echo \" molecule login -s $SCENARIO # SSH into test instance\"\n echo \" molecule verify -s $SCENARIO # Re-run verification\"\n echo \" molecule destroy -s $SCENARIO # Clean up when done\"\n fi\n exit 1\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":9648,"content_sha256":"79dc73f282ef244be6ab482c429142403711aa418935e49155502df57a99f905"},{"filename":"scripts/validate_inventory.sh","content":"#!/usr/bin/env bash\n\n# Ansible Inventory Validation Script\n# Validates inventory files and directories using ansible-inventory\n# Automatically installs ansible in a temporary venv if not available\n\nset -e\n\nINVENTORY=\"$1\"\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nSKILL_DIR=\"$(dirname \"$SCRIPT_DIR\")\"\n\nCOLOR_GREEN='\\033[0;32m'\nCOLOR_YELLOW='\\033[1;33m'\nCOLOR_RED='\\033[0;31m'\nCOLOR_BLUE='\\033[0;34m'\nCOLOR_RESET='\\033[0m'\n\n# Usage check\nif [ -z \"$INVENTORY\" ]; then\n echo \"Usage: $0 \u003cinventory-file|inventory-directory>\"\n echo \"\"\n echo \"Validates Ansible inventory files and directories.\"\n echo \"Checks syntax, structure, host/group resolution, and variable files.\"\n echo \"\"\n echo \"Examples:\"\n echo \" $0 inventory/hosts.yml\"\n echo \" $0 inventory/\"\n echo \" $0 production/\"\n exit 1\nfi\n\nif [ ! -f \"$INVENTORY\" ] && [ ! -d \"$INVENTORY\" ]; then\n echo -e \"${COLOR_RED}Error: Inventory not found: $INVENTORY${COLOR_RESET}\"\n exit 1\nfi\n\n# Get absolute path\nif [ -f \"$INVENTORY\" ]; then\n INVENTORY_ABS=$(cd \"$(dirname \"$INVENTORY\")\" && pwd)/$(basename \"$INVENTORY\")\n INVENTORY_DIR=$(dirname \"$INVENTORY_ABS\")\n SCAN_TYPE=\"file\"\nelse\n INVENTORY_ABS=$(cd \"$INVENTORY\" && pwd)\n INVENTORY_DIR=\"$INVENTORY_ABS\"\n SCAN_TYPE=\"directory\"\nfi\n\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}Ansible Inventory Validation${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho \"\"\necho \"Validating: $INVENTORY_ABS\"\necho \"\"\n\n# ─── Tool bootstrap ─────────────────────────────────────────────────────────\n\nTEMP_VENV=\"\"\nCLEANUP_VENV=0\n\nrun_ansible_inventory() {\n if [ -n \"$TEMP_VENV\" ]; then\n \"$TEMP_VENV/bin/ansible-inventory\" \"$@\"\n else\n ansible-inventory \"$@\"\n fi\n}\n\nresolve_yamllint_bin() {\n if [ -n \"$TEMP_VENV\" ] && [ -x \"$TEMP_VENV/bin/yamllint\" ]; then\n echo \"$TEMP_VENV/bin/yamllint\"\n return 0\n fi\n if command -v yamllint >/dev/null 2>&1; then\n command -v yamllint\n return 0\n fi\n return 1\n}\n\nMISSING_TOOLS=()\ncommand -v ansible-inventory >/dev/null 2>&1 || MISSING_TOOLS+=(\"ansible-inventory\")\n\nif [ ${#MISSING_TOOLS[@]} -gt 0 ]; then\n echo -e \"${COLOR_YELLOW}⚠ Missing tools: ${MISSING_TOOLS[*]}${COLOR_RESET}\"\n echo \"\"\n echo \"Creating temporary environment...\"\n TEMP_VENV=$(mktemp -d -t ansible-inv-validator.XXXXXX)\n CLEANUP_VENV=1\n\n cleanup() {\n if [ $CLEANUP_VENV -eq 1 ] && [ -n \"$TEMP_VENV\" ]; then\n echo \"\"\n echo \"Cleaning up temporary environment...\"\n rm -rf \"$TEMP_VENV\"\n fi\n }\n trap cleanup EXIT INT TERM\n\n echo \"Installing ansible + yamllint (this may take a minute)...\"\n python3 -m venv \"$TEMP_VENV\" >/dev/null 2>&1\n source \"$TEMP_VENV/bin/activate\"\n pip install --quiet --upgrade pip setuptools wheel\n pip install --quiet ansible yamllint\n echo -e \"${COLOR_GREEN}✓ Temporary environment ready${COLOR_RESET}\"\n echo \"\"\nelse\n echo -e \"${COLOR_GREEN}✓ ansible-inventory found$(ansible-inventory --version 2>/dev/null | head -1 | awk '{print \" - \"$0}')${COLOR_RESET}\"\n echo \"\"\nfi\n\nERRORS=0\nWARNINGS=0\n\n# ─── Stage 1: YAML syntax check on inventory YAML files ─────────────────────\n\necho -e \"${COLOR_BLUE}[1/4] YAML Syntax Check${COLOR_RESET}\"\necho \"-----------------------------------\"\n\nYAMLLINT_ARGS=()\nif [ -f \"$SKILL_DIR/assets/.yamllint\" ]; then\n YAMLLINT_ARGS=(-c \"$SKILL_DIR/assets/.yamllint\")\nfi\n\nYAMLLINT_BIN=\"$(resolve_yamllint_bin || true)\"\n\nif [ -n \"$YAMLLINT_BIN\" ]; then\n # Collect YAML files to lint (skip non-inventory files)\n if [ \"$SCAN_TYPE\" = \"file\" ]; then\n YAML_FILES=(\"$INVENTORY_ABS\")\n else\n mapfile -t YAML_FILES \u003c \u003c(find \"$INVENTORY_ABS\" -name \"*.yml\" -o -name \"*.yaml\" 2>/dev/null | grep -v \".git/\" || true)\n fi\n\n YAMLLINT_FAILED=0\n for yf in \"${YAML_FILES[@]}\"; do\n if OUTPUT=$(\"$YAMLLINT_BIN\" \"${YAMLLINT_ARGS[@]}\" \"$yf\" 2>&1); then\n if [ -n \"$OUTPUT\" ]; then\n echo \"$OUTPUT\"\n fi\n continue\n fi\n\n if echo \"$OUTPUT\" | grep -q \"error\"; then\n echo -e \"${COLOR_RED}✗ YAML errors in: $yf${COLOR_RESET}\"\n [ -n \"$OUTPUT\" ] && echo \"$OUTPUT\"\n YAMLLINT_FAILED=1\n else\n # warnings only\n [ -n \"$OUTPUT\" ] && echo \"$OUTPUT\"\n fi\n done\n\n if [ $YAMLLINT_FAILED -eq 1 ]; then\n echo -e \"${COLOR_RED}✗ YAML syntax errors detected${COLOR_RESET}\"\n ERRORS=$((ERRORS + 1))\n else\n echo -e \"${COLOR_GREEN}✓ YAML syntax check passed${COLOR_RESET}\"\n fi\nelse\n echo -e \"${COLOR_YELLOW}⚠ YAML syntax check SKIPPED (yamllint unavailable)${COLOR_RESET}\"\n WARNINGS=$((WARNINGS + 1))\nfi\necho \"\"\n\n# ─── Stage 2: Inventory parse and host list ──────────────────────────────────\n\necho -e \"${COLOR_BLUE}[2/4] Inventory Parse (ansible-inventory --list)${COLOR_RESET}\"\necho \"-----------------------------------\"\n\nLIST_STAGE_OK=0\nif LIST_OUTPUT=$(run_ansible_inventory -i \"$INVENTORY_ABS\" --list 2>&1); then\n LIST_STAGE_OK=1\n HOST_COUNT=$(echo \"$LIST_OUTPUT\" | python3 -c \"\nimport json, sys\ntry:\n data = json.load(sys.stdin)\n hosts = set()\n for k, v in data.items():\n if k == '_meta':\n hosts.update(v.get('hostvars', {}).keys())\n elif isinstance(v, dict):\n hosts.update(v.get('hosts', []))\n print(len(hosts))\nexcept Exception:\n print('?')\n\" 2>/dev/null || echo \"?\")\n echo -e \"${COLOR_GREEN}✓ Inventory parsed successfully${COLOR_RESET}\"\n echo \" Hosts resolved: $HOST_COUNT\"\nelse\n echo -e \"${COLOR_RED}✗ Inventory parse failed${COLOR_RESET}\"\n echo \"$LIST_OUTPUT\"\n ERRORS=$((ERRORS + 1))\nfi\necho \"\"\n\n# ─── Stage 3: Group/host graph ───────────────────────────────────────────────\n\necho -e \"${COLOR_BLUE}[3/4] Host Graph (ansible-inventory --graph)${COLOR_RESET}\"\necho \"-----------------------------------\"\n\nif GRAPH_OUTPUT=$(run_ansible_inventory -i \"$INVENTORY_ABS\" --graph 2>&1); then\n echo -e \"${COLOR_GREEN}✓ Host graph resolved${COLOR_RESET}\"\n echo \"\"\n echo \"$GRAPH_OUTPUT\"\nelse\n echo -e \"${COLOR_RED}✗ Graph resolution failed${COLOR_RESET}\"\n echo \"$GRAPH_OUTPUT\"\n ERRORS=$((ERRORS + 1))\nfi\necho \"\"\n\n# ─── Stage 4: Structure checks ───────────────────────────────────────────────\n\necho -e \"${COLOR_BLUE}[4/4] Structure and Best-Practice Checks${COLOR_RESET}\"\necho \"-----------------------------------\"\n\nSTRUCT_WARNINGS=0\n\n# Check for group_vars / host_vars alongside the inventory\ncheck_vars_dir() {\n local label=\"$1\"\n local path=\"$2\"\n if [ -d \"$path\" ]; then\n FILE_COUNT=$(find \"$path\" -name \"*.yml\" -o -name \"*.yaml\" | wc -l | tr -d ' ')\n echo -e \" ${COLOR_GREEN}✓${COLOR_RESET} $label/ found ($FILE_COUNT var files)\"\n fi\n}\n\nif [ \"$SCAN_TYPE\" = \"directory\" ]; then\n check_vars_dir \"group_vars\" \"$INVENTORY_ABS/group_vars\"\n check_vars_dir \"host_vars\" \"$INVENTORY_ABS/host_vars\"\nelse\n # File-based inventory — check sibling directories\n check_vars_dir \"group_vars\" \"$INVENTORY_DIR/group_vars\"\n check_vars_dir \"host_vars\" \"$INVENTORY_DIR/host_vars\"\nfi\n\n# Warn if any YAML inventory file contains 'ansible_password' in plaintext\nPASSWORD_HITS=0\nif [ \"$SCAN_TYPE\" = \"file\" ]; then\n PASSWORD_HITS=$(grep -c \"ansible_password:\" \"$INVENTORY_ABS\" 2>/dev/null || true)\nelse\n PASSWORD_HITS=$(grep -r -c \"ansible_password:\" \"$INVENTORY_ABS\" 2>/dev/null | awk -F: '{sum+=$2} END{print sum}' || true)\nfi\n\nif [ \"${PASSWORD_HITS:-0}\" -gt 0 ]; then\n echo -e \" ${COLOR_YELLOW}⚠${COLOR_RESET} ansible_password found in plaintext — use ansible-vault or SSH keys\"\n STRUCT_WARNINGS=$((STRUCT_WARNINGS + 1))\nfi\n\n# Warn if 'localhost' is in the parsed inventory without connection=local.\nif [ $LIST_STAGE_OK -eq 1 ]; then\n LOCALHOST_STATUS=$(printf '%s' \"$LIST_OUTPUT\" | python3 -c '\nimport json\nimport sys\n\ntry:\n data = json.load(sys.stdin)\nexcept Exception:\n print(\"parse_error\")\n raise SystemExit(0)\n\nhosts = set((data.get(\"_meta\") or {}).get(\"hostvars\", {}).keys())\nfor value in data.values():\n if isinstance(value, dict):\n for host in (value.get(\"hosts\") or []):\n if isinstance(host, str):\n hosts.add(host)\n\nif \"localhost\" not in hosts:\n print(\"absent\")\n raise SystemExit(0)\n\nhostvars = (data.get(\"_meta\") or {}).get(\"hostvars\", {}) or {}\nlocalhost_vars = hostvars.get(\"localhost\", {}) or {}\nconnection = localhost_vars.get(\"ansible_connection\", \"\")\nprint(\"ok\" if connection == \"local\" else \"warn\")\n')\n if [ \"$LOCALHOST_STATUS\" = \"warn\" ]; then\n echo -e \" ${COLOR_YELLOW}⚠${COLOR_RESET} 'localhost' defined without ansible_connection=local\"\n STRUCT_WARNINGS=$((STRUCT_WARNINGS + 1))\n elif [ \"$LOCALHOST_STATUS\" = \"parse_error\" ]; then\n echo -e \" ${COLOR_YELLOW}⚠${COLOR_RESET} Unable to evaluate localhost connection from inventory JSON\"\n STRUCT_WARNINGS=$((STRUCT_WARNINGS + 1))\n fi\nfi\n\nif [ $STRUCT_WARNINGS -eq 0 ]; then\n echo -e \" ${COLOR_GREEN}✓${COLOR_RESET} No structural issues found\"\nfi\n\nWARNINGS=$((WARNINGS + STRUCT_WARNINGS))\necho \"\"\n\n# ─── Summary ─────────────────────────────────────────────────────────────────\n\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}Inventory Validation Summary${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\n\nif [ $ERRORS -eq 0 ] && [ $WARNINGS -eq 0 ]; then\n echo -e \"${COLOR_GREEN}✓ Inventory is valid — no issues detected!${COLOR_RESET}\"\n if [ -n \"$TEMP_VENV\" ]; then\n echo \"\"\n echo \"Note: ansible was installed in a temporary environment.\"\n echo \"To install permanently: pip3 install ansible\"\n fi\n exit 0\nelif [ $ERRORS -eq 0 ]; then\n echo -e \"${COLOR_YELLOW}⚠ Inventory validation completed with $WARNINGS warning(s)${COLOR_RESET}\"\n echo \"\"\n echo \"Review warnings above and consider:\"\n echo \" - Using Ansible Vault for any plaintext credentials\"\n echo \" - Defining ansible_connection=local for localhost entries\"\n echo \" - Consulting references/best_practices.md for inventory organisation\"\n if [ -n \"$TEMP_VENV\" ]; then\n echo \"\"\n echo \"Note: ansible was installed in a temporary environment.\"\n echo \"To install permanently: pip3 install ansible\"\n fi\n exit 0\nelse\n echo -e \"${COLOR_RED}✗ Inventory validation failed with $ERRORS error(s) and $WARNINGS warning(s)${COLOR_RESET}\"\n echo \"\"\n echo \"Remediation steps:\"\n echo \" 1. Fix YAML syntax errors reported above\"\n echo \" 2. Ensure host/group names follow Ansible naming rules\"\n echo \" 3. Verify all referenced groups and hosts are defined\"\n echo \" 4. Consult references/common_errors.md for inventory error solutions\"\n exit 1\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":11514,"content_sha256":"7ab3f1f8f527aff89d9b0920656d96677ffb28f5cf8279ee6105a51a249c38ac"},{"filename":"scripts/validate_playbook_security.sh","content":"#!/usr/bin/env bash\n\n# Ansible Playbook Security Validation Script using Checkov\n# Automatically installs checkov in temporary venv if not available\n\nset -e\n\nPLAYBOOK=\"$1\"\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nSKILL_DIR=\"$(dirname \"$SCRIPT_DIR\")\"\n\nCOLOR_GREEN='\\033[0;32m'\nCOLOR_YELLOW='\\033[1;33m'\nCOLOR_RED='\\033[0;31m'\nCOLOR_BLUE='\\033[0;34m'\nCOLOR_RESET='\\033[0m'\n\n# Usage check\nif [ -z \"$PLAYBOOK\" ]; then\n echo \"Usage: $0 \u003cplaybook.yml|playbook-directory>\"\n exit 1\nfi\n\nif [ ! -f \"$PLAYBOOK\" ] && [ ! -d \"$PLAYBOOK\" ]; then\n echo -e \"${COLOR_RED}Error: Playbook or directory not found: $PLAYBOOK${COLOR_RESET}\"\n exit 1\nfi\n\n# Get absolute path\nif [ -f \"$PLAYBOOK\" ]; then\n PLAYBOOK_ABS=$(cd \"$(dirname \"$PLAYBOOK\")\" && pwd)/$(basename \"$PLAYBOOK\")\n SCAN_TYPE=\"file\"\nelse\n PLAYBOOK_ABS=$(cd \"$PLAYBOOK\" && pwd)\n SCAN_TYPE=\"directory\"\nfi\n\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}Ansible Security Validation (Checkov)${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho \"\"\necho \"Scanning: $PLAYBOOK_ABS\"\necho \"\"\n\n# Check for checkov and setup venv if needed\nTEMP_VENV=\"\"\nCLEANUP_VENV=0\nUSE_SYSTEM_CHECKOV=1\n\n# Function to run checkov command\nrun_checkov() {\n if [ -n \"$TEMP_VENV\" ]; then\n \"$TEMP_VENV/bin/checkov\" \"$@\"\n else\n checkov \"$@\"\n fi\n}\n\n# Check if checkov is available\nif ! command -v checkov >/dev/null 2>&1; then\n USE_SYSTEM_CHECKOV=0\nfi\n\n# Create temp venv if checkov is missing\nif [ $USE_SYSTEM_CHECKOV -eq 0 ]; then\n echo -e \"${COLOR_YELLOW}⚠ checkov not found in system${COLOR_RESET}\"\n echo \"\"\n echo \"Creating temporary environment with checkov...\"\n echo \"\"\n\n # Create temporary venv\n TEMP_VENV=$(mktemp -d -t checkov-validator.XXXXXX)\n CLEANUP_VENV=1\n\n # Setup cleanup trap\n cleanup() {\n if [ $CLEANUP_VENV -eq 1 ] && [ -n \"$TEMP_VENV\" ]; then\n echo \"\"\n echo \"Cleaning up temporary environment...\"\n rm -rf \"$TEMP_VENV\"\n fi\n }\n trap cleanup EXIT INT TERM\n\n # Create venv and install checkov\n echo \"Installing checkov (this may take a minute)...\"\n python3 -m venv \"$TEMP_VENV\" >/dev/null 2>&1\n\n # Activate venv and install\n source \"$TEMP_VENV/bin/activate\"\n\n # Install checkov\n pip install --quiet --upgrade pip setuptools wheel\n pip install --quiet checkov\n\n echo -e \"${COLOR_GREEN}✓ Temporary environment ready${COLOR_RESET}\"\n echo \"\"\nelse\n echo -e \"${COLOR_GREEN}✓ Using system checkov${COLOR_RESET}\"\n echo \"\"\nfi\n\nERRORS=0\nWARNINGS=0\n\n# Security Scan with Checkov\necho -e \"${COLOR_BLUE}[1/1] Security Scan (Checkov)${COLOR_RESET}\"\necho \"-----------------------------------\"\n\n# Prepare checkov command based on scan type\nif [ \"$SCAN_TYPE\" = \"file\" ]; then\n # Use -f for single file to avoid scanning sibling files in the same directory\n CHECKOV_ARGS=(\"-f\" \"$PLAYBOOK_ABS\" \"--framework\" \"ansible\" \"--compact\" \"--quiet\")\nelse\n # For directory, scan it directly\n CHECKOV_ARGS=(\"-d\" \"$PLAYBOOK_ABS\" \"--framework\" \"ansible\" \"--compact\" \"--quiet\")\nfi\n\n# Run checkov and capture output\nif CHECKOV_OUTPUT=$(run_checkov \"${CHECKOV_ARGS[@]}\" 2>&1); then\n CHECKOV_EXIT=0\nelse\n CHECKOV_EXIT=$?\nfi\n\n# Parse checkov output\nif echo \"$CHECKOV_OUTPUT\" | grep -q \"Passed checks:\"; then\n # Extract individual counters from the single-line summary:\n # \"Passed checks: 4, Failed checks: 2, Skipped checks: 0\"\n # Strip everything before each label, then strip comma-and-remainder.\n PASSED=$(echo \"$CHECKOV_OUTPUT\" | awk '/Passed checks:/{gsub(/.*Passed checks: /,\"\"); gsub(/,.*/,\"\"); print}' || echo \"0\")\n FAILED=$(echo \"$CHECKOV_OUTPUT\" | awk '/Failed checks:/{gsub(/.*Failed checks: /,\"\"); gsub(/,.*/,\"\"); print}' || echo \"0\")\n SKIPPED=$(echo \"$CHECKOV_OUTPUT\" | awk '/Skipped checks:/{gsub(/.*Skipped checks: /,\"\"); gsub(/[^0-9].*/,\"\"); print}' || echo \"0\")\n\n echo -e \"Security Scan Results:\"\n echo -e \" ${COLOR_GREEN}Passed:${COLOR_RESET} $PASSED checks\"\n echo -e \" ${COLOR_RED}Failed:${COLOR_RESET} $FAILED checks\"\n echo -e \" ${COLOR_YELLOW}Skipped:${COLOR_RESET} $SKIPPED checks\"\n echo \"\"\n\n if [ \"$FAILED\" -gt 0 ]; then\n echo -e \"${COLOR_RED}✗ Security issues detected${COLOR_RESET}\"\n echo \"\"\n echo \"Failed Checks:\"\n echo \"$CHECKOV_OUTPUT\" | grep -A 3 \"Check:\" | grep -v \"^--$\" || true\n echo \"\"\n echo \"Common Security Issues:\"\n echo \" - CKV_ANSIBLE_1: URI module disabling certificate validation\"\n echo \" - CKV_ANSIBLE_2: get_url disabling certificate validation\"\n echo \" - CKV_ANSIBLE_3: yum disabling certificate validation\"\n echo \" - CKV_ANSIBLE_5: apt installing packages without GPG signature\"\n echo \" - CKV2_ANSIBLE_1/2: Using HTTP instead of HTTPS\"\n echo \"\"\n echo \"For detailed policy documentation, visit:\"\n echo \" https://www.checkov.io/5.Policy%20Index/ansible.html\"\n ERRORS=$((ERRORS + 1))\n else\n echo -e \"${COLOR_GREEN}✓ All security checks passed${COLOR_RESET}\"\n fi\nelif echo \"$CHECKOV_OUTPUT\" | grep -q \"No Ansible files found\"; then\n echo -e \"${COLOR_YELLOW}⚠ No Ansible files found to scan${COLOR_RESET}\"\n WARNINGS=$((WARNINGS + 1))\nelse\n echo -e \"${COLOR_RED}✗ Checkov scan failed${COLOR_RESET}\"\n echo \"$CHECKOV_OUTPUT\"\n ERRORS=$((ERRORS + 1))\nfi\n\necho \"\"\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}Security Validation Summary${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\n\nif [ $ERRORS -eq 0 ] && [ $WARNINGS -eq 0 ]; then\n echo -e \"${COLOR_GREEN}✓ No security issues detected!${COLOR_RESET}\"\n if [ -n \"$TEMP_VENV\" ]; then\n echo \"\"\n echo \"Note: checkov was installed in a temporary environment.\"\n echo \"To install permanently: pip3 install checkov\"\n fi\n exit 0\nelif [ $ERRORS -eq 0 ]; then\n echo -e \"${COLOR_YELLOW}⚠ Scan completed with $WARNINGS warning(s)${COLOR_RESET}\"\n if [ -n \"$TEMP_VENV\" ]; then\n echo \"\"\n echo \"Note: checkov was installed in a temporary environment.\"\n echo \"To install permanently: pip3 install checkov\"\n fi\n exit 0\nelse\n echo -e \"${COLOR_RED}✗ Security validation failed with $FAILED security issue(s)${COLOR_RESET}\"\n echo \"\"\n echo \"Recommendations:\"\n echo \" 1. Review failed security checks above\"\n echo \" 2. Consult Checkov policy documentation for remediation\"\n echo \" 3. Use HTTPS URLs for downloads and enable certificate validation\"\n echo \" 4. Ensure package signatures are verified (GPG)\"\n echo \" 5. Follow security best practices from references/security_checklist.md\"\n exit 1\nfi","content_type":"application/x-sh; charset=utf-8","language":"bash","size":6845,"content_sha256":"3e19eba3cec984ea42c3d524f68850cc8883336ef012a2f0f3cbb88f79c010e1"},{"filename":"scripts/validate_playbook.sh","content":"#!/usr/bin/env bash\n\n# Comprehensive Ansible Playbook Validation Script\n# Automatically installs ansible and ansible-lint in temporary venv if not available\n\nset -e\n\nPLAYBOOK=\"$1\"\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nSKILL_DIR=\"$(dirname \"$SCRIPT_DIR\")\"\n\nCOLOR_GREEN='\\033[0;32m'\nCOLOR_YELLOW='\\033[1;33m'\nCOLOR_RED='\\033[0;31m'\nCOLOR_BLUE='\\033[0;34m'\nCOLOR_RESET='\\033[0m'\n\n# Usage check\nif [ -z \"$PLAYBOOK\" ]; then\n echo \"Usage: $0 \u003cplaybook.yml>\"\n exit 1\nfi\n\nif [ ! -f \"$PLAYBOOK\" ]; then\n echo -e \"${COLOR_RED}Error: Playbook not found: $PLAYBOOK${COLOR_RESET}\"\n exit 1\nfi\n\n# Get absolute path to playbook\nPLAYBOOK_ABS=$(cd \"$(dirname \"$PLAYBOOK\")\" && pwd)/$(basename \"$PLAYBOOK\")\n\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}Ansible Playbook Validation${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho \"\"\necho \"Validating: $PLAYBOOK_ABS\"\necho \"\"\n\n# Check for required tools and setup venv if needed\nTEMP_VENV=\"\"\nCLEANUP_VENV=0\nUSE_SYSTEM_ANSIBLE=1\nUSE_SYSTEM_ANSIBLE_LINT=1\n\n# Function to run ansible-playbook command\nrun_ansible_playbook() {\n if [ -n \"$TEMP_VENV\" ]; then\n \"$TEMP_VENV/bin/ansible-playbook\" \"$@\"\n else\n ansible-playbook \"$@\"\n fi\n}\n\n# Function to run ansible-lint command\nrun_ansible_lint() {\n if [ -n \"$TEMP_VENV\" ]; then\n \"$TEMP_VENV/bin/ansible-lint\" \"$@\"\n else\n ansible-lint \"$@\"\n fi\n}\n\n# Check if ansible-playbook is available\nif ! command -v ansible-playbook >/dev/null 2>&1; then\n USE_SYSTEM_ANSIBLE=0\nfi\n\n# Check if ansible-lint is available\nif ! command -v ansible-lint >/dev/null 2>&1; then\n USE_SYSTEM_ANSIBLE_LINT=0\nfi\n\n# Create temp venv if either tool is missing\nif [ $USE_SYSTEM_ANSIBLE -eq 0 ] || [ $USE_SYSTEM_ANSIBLE_LINT -eq 0 ]; then\n echo -e \"${COLOR_YELLOW}⚠ Some tools not found in system${COLOR_RESET}\"\n\n if [ $USE_SYSTEM_ANSIBLE -eq 0 ]; then\n echo \" - ansible-playbook: not found\"\n else\n echo \" - ansible-playbook: using system version\"\n fi\n\n if [ $USE_SYSTEM_ANSIBLE_LINT -eq 0 ]; then\n echo \" - ansible-lint: not found\"\n else\n echo \" - ansible-lint: using system version\"\n fi\n\n echo \"\"\n echo \"Creating temporary environment with missing tools...\"\n echo \"\"\n\n # Create temporary venv\n TEMP_VENV=$(mktemp -d -t ansible-validator.XXXXXX)\n CLEANUP_VENV=1\n\n # Setup cleanup trap\n cleanup() {\n if [ $CLEANUP_VENV -eq 1 ] && [ -n \"$TEMP_VENV\" ]; then\n echo \"\"\n echo \"Cleaning up temporary environment...\"\n rm -rf \"$TEMP_VENV\"\n fi\n }\n trap cleanup EXIT INT TERM\n\n # Create venv and install tools\n echo \"Installing ansible tools (this may take a minute)...\"\n python3 -m venv \"$TEMP_VENV\" >/dev/null 2>&1\n\n # Activate venv and install\n source \"$TEMP_VENV/bin/activate\"\n\n # Install required tools\n pip install --quiet --upgrade pip setuptools wheel\n pip install --quiet ansible-core ansible-lint yamllint\n\n echo -e \"${COLOR_GREEN}✓ Temporary environment ready${COLOR_RESET}\"\n echo \"\"\nelse\n echo -e \"${COLOR_GREEN}✓ Using system ansible tools${COLOR_RESET}\"\n echo \"\"\nfi\n\nERRORS=0\nWARNINGS=0\n\n# Stage 1: YAML Syntax Check\necho -e \"${COLOR_BLUE}[1/3] YAML Syntax Check (yamllint)${COLOR_RESET}\"\necho \"-----------------------------------\"\n\n# yamllint - prefer system, fallback to venv\nYAMLLINT_CMD=\"\"\nif command -v yamllint >/dev/null 2>&1; then\n YAMLLINT_CMD=\"yamllint\"\nelif [ -n \"$TEMP_VENV\" ] && [ -f \"$TEMP_VENV/bin/yamllint\" ]; then\n YAMLLINT_CMD=\"$TEMP_VENV/bin/yamllint\"\nfi\n\nif [ -n \"$YAMLLINT_CMD\" ]; then\n # Check if custom config exists\n if [ -f \"$SKILL_DIR/assets/.yamllint\" ]; then\n YAMLLINT_CONFIG=\"-c $SKILL_DIR/assets/.yamllint\"\n else\n YAMLLINT_CONFIG=\"\"\n fi\n\n if $YAMLLINT_CMD $YAMLLINT_CONFIG \"$PLAYBOOK_ABS\"; then\n echo -e \"${COLOR_GREEN}✓ YAML syntax check passed${COLOR_RESET}\"\n else\n echo -e \"${COLOR_RED}✗ YAML syntax check failed${COLOR_RESET}\"\n ERRORS=$((ERRORS + 1))\n fi\nelse\n echo -e \"${COLOR_YELLOW}⚠ yamllint not available - skipping YAML syntax check${COLOR_RESET}\"\n WARNINGS=$((WARNINGS + 1))\nfi\n\necho \"\"\n\n# Stage 2: Ansible Syntax Check\necho -e \"${COLOR_BLUE}[2/3] Ansible Syntax Check${COLOR_RESET}\"\necho \"-----------------------------------\"\n\nif [ $USE_SYSTEM_ANSIBLE -eq 1 ] || [ -n \"$TEMP_VENV\" ]; then\n if run_ansible_playbook --syntax-check \"$PLAYBOOK_ABS\"; then\n echo -e \"${COLOR_GREEN}✓ Ansible syntax check passed${COLOR_RESET}\"\n else\n echo -e \"${COLOR_RED}✗ Ansible syntax check failed${COLOR_RESET}\"\n ERRORS=$((ERRORS + 1))\n fi\nelse\n echo -e \"${COLOR_RED}✗ ansible-playbook not available${COLOR_RESET}\"\n ERRORS=$((ERRORS + 1))\nfi\n\necho \"\"\n\n# Stage 3: Ansible Lint\necho -e \"${COLOR_BLUE}[3/3] Ansible Lint (ansible-lint)${COLOR_RESET}\"\necho \"-----------------------------------\"\n\nif [ $USE_SYSTEM_ANSIBLE_LINT -eq 1 ] || [ -n \"$TEMP_VENV\" ]; then\n # Check if custom config exists\n if [ -f \"$SKILL_DIR/assets/.ansible-lint\" ]; then\n ANSIBLE_LINT_CONFIG=\"-c $SKILL_DIR/assets/.ansible-lint\"\n else\n ANSIBLE_LINT_CONFIG=\"\"\n fi\n\n # Run ansible-lint and capture exit code\n if run_ansible_lint $ANSIBLE_LINT_CONFIG \"$PLAYBOOK_ABS\"; then\n echo -e \"${COLOR_GREEN}✓ Ansible lint check passed${COLOR_RESET}\"\n else\n LINT_EXIT=$?\n if [ $LINT_EXIT -eq 2 ]; then\n echo -e \"${COLOR_RED}✗ Ansible lint found errors${COLOR_RESET}\"\n ERRORS=$((ERRORS + 1))\n else\n echo -e \"${COLOR_YELLOW}⚠ Ansible lint found warnings${COLOR_RESET}\"\n WARNINGS=$((WARNINGS + 1))\n fi\n fi\nelse\n echo -e \"${COLOR_YELLOW}⚠ ansible-lint not available - skipping lint check${COLOR_RESET}\"\n WARNINGS=$((WARNINGS + 1))\nfi\n\necho \"\"\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}Validation Summary${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\n\nif [ $ERRORS -eq 0 ] && [ $WARNINGS -eq 0 ]; then\n echo -e \"${COLOR_GREEN}✓ All checks passed successfully!${COLOR_RESET}\"\n echo \"\"\n echo \"For best practices, see: $SKILL_DIR/references/best_practices.md\"\n if [ -n \"$TEMP_VENV\" ]; then\n echo \"\"\n echo \"Note: Some tools were installed in a temporary environment.\"\n echo \"To install permanently: pip install ansible ansible-lint yamllint\"\n fi\n exit 0\nelif [ $ERRORS -eq 0 ]; then\n echo -e \"${COLOR_YELLOW}⚠ Validation completed with $WARNINGS warning(s)${COLOR_RESET}\"\n echo \"\"\n echo \"For best practices, see: $SKILL_DIR/references/best_practices.md\"\n if [ -n \"$TEMP_VENV\" ]; then\n echo \"\"\n echo \"Note: Some tools were installed in a temporary environment.\"\n echo \"To install permanently: pip install ansible ansible-lint yamllint\"\n fi\n exit 0\nelse\n echo -e \"${COLOR_RED}✗ Validation failed with $ERRORS error(s) and $WARNINGS warning(s)${COLOR_RESET}\"\n echo \"\"\n echo \"For troubleshooting, see: $SKILL_DIR/references/common_errors.md\"\n echo \"For best practices, see: $SKILL_DIR/references/best_practices.md\"\n exit 1\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":7348,"content_sha256":"455b15c1bb8daf77d0336cee08a10fe9f2a3034b097d8ac227bafe12f9d44575"},{"filename":"scripts/validate_role_security.sh","content":"#!/usr/bin/env bash\n\n# Ansible Role Security Validation Script using Checkov\n# Automatically installs checkov in temporary venv if not available\n\nset -e\n\nROLE_DIR=\"$1\"\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nSKILL_DIR=\"$(dirname \"$SCRIPT_DIR\")\"\n\nCOLOR_GREEN='\\033[0;32m'\nCOLOR_YELLOW='\\033[1;33m'\nCOLOR_RED='\\033[0;31m'\nCOLOR_BLUE='\\033[0;34m'\nCOLOR_RESET='\\033[0m'\n\n# Usage check\nif [ -z \"$ROLE_DIR\" ]; then\n echo \"Usage: $0 \u003crole-directory>\"\n exit 1\nfi\n\nif [ ! -d \"$ROLE_DIR\" ]; then\n echo -e \"${COLOR_RED}Error: Role directory not found: $ROLE_DIR${COLOR_RESET}\"\n exit 1\nfi\n\n# Get absolute path to role\nROLE_ABS_PATH=$(cd \"$ROLE_DIR\" && pwd)\n\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}Ansible Role Security Validation${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho \"\"\necho \"Scanning: $ROLE_ABS_PATH\"\necho \"\"\n\n# Check for checkov and setup venv if needed\nTEMP_VENV=\"\"\nCLEANUP_VENV=0\nUSE_SYSTEM_CHECKOV=1\n\n# Function to run checkov command\nrun_checkov() {\n if [ -n \"$TEMP_VENV\" ]; then\n \"$TEMP_VENV/bin/checkov\" \"$@\"\n else\n checkov \"$@\"\n fi\n}\n\n# Check if checkov is available\nif ! command -v checkov >/dev/null 2>&1; then\n USE_SYSTEM_CHECKOV=0\nfi\n\n# Create temp venv if checkov is missing\nif [ $USE_SYSTEM_CHECKOV -eq 0 ]; then\n echo -e \"${COLOR_YELLOW}⚠ checkov not found in system${COLOR_RESET}\"\n echo \"\"\n echo \"Creating temporary environment with checkov...\"\n echo \"\"\n\n # Create temporary venv\n TEMP_VENV=$(mktemp -d -t checkov-validator.XXXXXX)\n CLEANUP_VENV=1\n\n # Setup cleanup trap\n cleanup() {\n if [ $CLEANUP_VENV -eq 1 ] && [ -n \"$TEMP_VENV\" ]; then\n echo \"\"\n echo \"Cleaning up temporary environment...\"\n rm -rf \"$TEMP_VENV\"\n fi\n }\n trap cleanup EXIT INT TERM\n\n # Create venv and install checkov\n echo \"Installing checkov (this may take a minute)...\"\n python3 -m venv \"$TEMP_VENV\" >/dev/null 2>&1\n\n # Activate venv and install\n source \"$TEMP_VENV/bin/activate\"\n\n # Install checkov\n pip install --quiet --upgrade pip setuptools wheel\n pip install --quiet checkov\n\n echo -e \"${COLOR_GREEN}✓ Temporary environment ready${COLOR_RESET}\"\n echo \"\"\nelse\n echo -e \"${COLOR_GREEN}✓ Using system checkov${COLOR_RESET}\"\n echo \"\"\nfi\n\nERRORS=0\nWARNINGS=0\n\n# Security Scan with Checkov\necho -e \"${COLOR_BLUE}[1/1] Security Scan (Checkov)${COLOR_RESET}\"\necho \"-----------------------------------\"\n\n# Run checkov on the role directory\nCHECKOV_ARGS=(\"-d\" \"$ROLE_ABS_PATH\" \"--framework\" \"ansible\" \"--compact\" \"--quiet\")\n\n# Run checkov and capture output\nif CHECKOV_OUTPUT=$(run_checkov \"${CHECKOV_ARGS[@]}\" 2>&1); then\n CHECKOV_EXIT=0\nelse\n CHECKOV_EXIT=$?\nfi\n\n# Parse checkov output\nif echo \"$CHECKOV_OUTPUT\" | grep -q \"Passed checks:\"; then\n # Extract individual counters from the single-line summary:\n # \"Passed checks: 4, Failed checks: 2, Skipped checks: 0\"\n # Strip everything before each label, then strip comma-and-remainder.\n PASSED=$(echo \"$CHECKOV_OUTPUT\" | awk '/Passed checks:/{gsub(/.*Passed checks: /,\"\"); gsub(/,.*/,\"\"); print}' || echo \"0\")\n FAILED=$(echo \"$CHECKOV_OUTPUT\" | awk '/Failed checks:/{gsub(/.*Failed checks: /,\"\"); gsub(/,.*/,\"\"); print}' || echo \"0\")\n SKIPPED=$(echo \"$CHECKOV_OUTPUT\" | awk '/Skipped checks:/{gsub(/.*Skipped checks: /,\"\"); gsub(/[^0-9].*/,\"\"); print}' || echo \"0\")\n\n echo -e \"Security Scan Results:\"\n echo -e \" ${COLOR_GREEN}Passed:${COLOR_RESET} $PASSED checks\"\n echo -e \" ${COLOR_RED}Failed:${COLOR_RESET} $FAILED checks\"\n echo -e \" ${COLOR_YELLOW}Skipped:${COLOR_RESET} $SKIPPED checks\"\n echo \"\"\n\n if [ \"$FAILED\" -gt 0 ]; then\n echo -e \"${COLOR_RED}✗ Security issues detected in role${COLOR_RESET}\"\n echo \"\"\n echo \"Failed Checks:\"\n echo \"$CHECKOV_OUTPUT\" | grep -A 3 \"Check:\" | grep -v \"^--$\" || true\n echo \"\"\n echo \"Common Security Issues in Roles:\"\n echo \" - Certificate validation disabled in uri, get_url, yum modules\"\n echo \" - Using HTTP instead of HTTPS for downloads\"\n echo \" - Installing packages without GPG signature verification\"\n echo \" - Insecure privilege escalation patterns\"\n echo \" - Missing error handling in critical tasks\"\n echo \"\"\n echo \"Recommendations:\"\n echo \" 1. Enable SSL/TLS certificate validation in all modules\"\n echo \" 2. Use HTTPS URLs for all downloads and package repositories\"\n echo \" 3. Verify GPG signatures for packages (apt, yum, dnf)\"\n echo \" 4. Implement proper error handling with block/rescue\"\n echo \" 5. Follow principle of least privilege for become/sudo\"\n echo \"\"\n echo \"For detailed policy documentation, visit:\"\n echo \" https://www.checkov.io/5.Policy%20Index/ansible.html\"\n echo \"\"\n echo \"For security best practices, see:\"\n echo \" $SKILL_DIR/references/security_checklist.md\"\n ERRORS=$((ERRORS + 1))\n else\n echo -e \"${COLOR_GREEN}✓ All security checks passed${COLOR_RESET}\"\n fi\nelif echo \"$CHECKOV_OUTPUT\" | grep -q \"No Ansible files found\"; then\n echo -e \"${COLOR_YELLOW}⚠ No Ansible files found in role${COLOR_RESET}\"\n echo \" Make sure the role has tasks/ directory with YAML files\"\n WARNINGS=$((WARNINGS + 1))\nelse\n echo -e \"${COLOR_RED}✗ Checkov scan failed${COLOR_RESET}\"\n echo \"$CHECKOV_OUTPUT\"\n ERRORS=$((ERRORS + 1))\nfi\n\necho \"\"\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}Security Validation Summary${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\n\nif [ $ERRORS -eq 0 ] && [ $WARNINGS -eq 0 ]; then\n echo -e \"${COLOR_GREEN}✓ No security issues detected!${COLOR_RESET}\"\n echo \"\"\n echo \"The role follows security best practices.\"\n if [ -n \"$TEMP_VENV\" ]; then\n echo \"\"\n echo \"Note: checkov was installed in a temporary environment.\"\n echo \"To install permanently: pip3 install checkov\"\n fi\n exit 0\nelif [ $ERRORS -eq 0 ]; then\n echo -e \"${COLOR_YELLOW}⚠ Scan completed with $WARNINGS warning(s)${COLOR_RESET}\"\n if [ -n \"$TEMP_VENV\" ]; then\n echo \"\"\n echo \"Note: checkov was installed in a temporary environment.\"\n echo \"To install permanently: pip3 install checkov\"\n fi\n exit 0\nelse\n echo -e \"${COLOR_RED}✗ Security validation failed with $FAILED security issue(s)${COLOR_RESET}\"\n echo \"\"\n echo \"Next Steps:\"\n echo \" 1. Review the failed checks listed above\"\n echo \" 2. Update role tasks to address security issues\"\n echo \" 3. Re-run this security scan to verify fixes\"\n echo \" 4. Run full validation: bash scripts/validate_role.sh $ROLE_ABS_PATH\"\n exit 1\nfi","content_type":"application/x-sh; charset=utf-8","language":"bash","size":6927,"content_sha256":"d73c55b7306cb1b424b10a2fbbbc326c3670fd35464ecd717c8ec497fb665890"},{"filename":"scripts/validate_role.sh","content":"#!/usr/bin/env bash\n\n# Comprehensive Ansible Role Validation Script\n# Automatically installs ansible and ansible-lint in temporary venv if not available\n\nset -e\n\nROLE_DIR=\"$1\"\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nSKILL_DIR=\"$(dirname \"$SCRIPT_DIR\")\"\n\nCOLOR_GREEN='\\033[0;32m'\nCOLOR_YELLOW='\\033[1;33m'\nCOLOR_RED='\\033[0;31m'\nCOLOR_BLUE='\\033[0;34m'\nCOLOR_RESET='\\033[0m'\n\n# Usage check\nif [ -z \"$ROLE_DIR\" ]; then\n echo \"Usage: $0 \u003crole-directory>\"\n exit 1\nfi\n\nif [ ! -d \"$ROLE_DIR\" ]; then\n echo -e \"${COLOR_RED}Error: Role directory not found: $ROLE_DIR${COLOR_RESET}\"\n exit 1\nfi\n\n# Get absolute path to role\nROLE_ABS_PATH=$(cd \"$ROLE_DIR\" && pwd)\n\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}Ansible Role Validation${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho \"\"\necho \"Validating: $ROLE_ABS_PATH\"\necho \"\"\n\n# Check for required tools and setup venv if needed\nTEMP_VENV=\"\"\nCLEANUP_VENV=0\nUSE_SYSTEM_ANSIBLE=1\nUSE_SYSTEM_ANSIBLE_LINT=1\n\n# Function to run ansible-playbook command\nrun_ansible_playbook() {\n if [ -n \"$TEMP_VENV\" ]; then\n \"$TEMP_VENV/bin/ansible-playbook\" \"$@\"\n else\n ansible-playbook \"$@\"\n fi\n}\n\n# Function to run ansible-lint command\nrun_ansible_lint() {\n if [ -n \"$TEMP_VENV\" ]; then\n \"$TEMP_VENV/bin/ansible-lint\" \"$@\"\n else\n ansible-lint \"$@\"\n fi\n}\n\n# Check if ansible-playbook is available\nif ! command -v ansible-playbook >/dev/null 2>&1; then\n USE_SYSTEM_ANSIBLE=0\nfi\n\n# Check if ansible-lint is available\nif ! command -v ansible-lint >/dev/null 2>&1; then\n USE_SYSTEM_ANSIBLE_LINT=0\nfi\n\n# Create temp venv if either tool is missing\nif [ $USE_SYSTEM_ANSIBLE -eq 0 ] || [ $USE_SYSTEM_ANSIBLE_LINT -eq 0 ]; then\n echo -e \"${COLOR_YELLOW}⚠ Some tools not found in system${COLOR_RESET}\"\n\n if [ $USE_SYSTEM_ANSIBLE -eq 0 ]; then\n echo \" - ansible-playbook: not found\"\n else\n echo \" - ansible-playbook: using system version\"\n fi\n\n if [ $USE_SYSTEM_ANSIBLE_LINT -eq 0 ]; then\n echo \" - ansible-lint: not found\"\n else\n echo \" - ansible-lint: using system version\"\n fi\n\n echo \"\"\n echo \"Creating temporary environment with missing tools...\"\n echo \"\"\n\n # Create temporary venv\n TEMP_VENV=$(mktemp -d -t ansible-validator.XXXXXX)\n CLEANUP_VENV=1\n\n # Setup cleanup trap\n cleanup() {\n if [ $CLEANUP_VENV -eq 1 ] && [ -n \"$TEMP_VENV\" ]; then\n echo \"\"\n echo \"Cleaning up temporary environment...\"\n rm -rf \"$TEMP_VENV\"\n fi\n }\n trap cleanup EXIT INT TERM\n\n # Create venv and install tools\n echo \"Installing ansible tools (this may take a minute)...\"\n python3 -m venv \"$TEMP_VENV\" >/dev/null 2>&1\n\n # Activate venv and install\n source \"$TEMP_VENV/bin/activate\"\n\n # Install required tools\n pip install --quiet --upgrade pip setuptools wheel\n pip install --quiet ansible-core ansible-lint yamllint\n\n echo -e \"${COLOR_GREEN}✓ Temporary environment ready${COLOR_RESET}\"\n echo \"\"\nelse\n echo -e \"${COLOR_GREEN}✓ Using system ansible tools${COLOR_RESET}\"\n echo \"\"\nfi\n\nERRORS=0\nWARNINGS=0\n\n# Stage 1: Role Structure Check\necho -e \"${COLOR_BLUE}[1/5] Role Structure Check${COLOR_RESET}\"\necho \"-----------------------------------\"\n\nREQUIRED_DIRS=(\"tasks\")\nRECOMMENDED_DIRS=(\"defaults\" \"handlers\" \"meta\" \"templates\" \"vars\")\nOPTIONAL_DIRS=(\"files\" \"molecule\")\n\necho \"Checking required directories...\"\nfor dir in \"${REQUIRED_DIRS[@]}\"; do\n if [ -d \"$ROLE_ABS_PATH/$dir\" ]; then\n echo -e \" ${COLOR_GREEN}✓${COLOR_RESET} $dir/\"\n else\n echo -e \" ${COLOR_RED}✗${COLOR_RESET} $dir/ - REQUIRED\"\n ERRORS=$((ERRORS + 1))\n fi\ndone\n\necho \"\"\necho \"Checking recommended directories...\"\nfor dir in \"${RECOMMENDED_DIRS[@]}\"; do\n if [ -d \"$ROLE_ABS_PATH/$dir\" ]; then\n echo -e \" ${COLOR_GREEN}✓${COLOR_RESET} $dir/\"\n else\n echo -e \" ${COLOR_YELLOW}⚠${COLOR_RESET} $dir/ - recommended\"\n WARNINGS=$((WARNINGS + 1))\n fi\ndone\n\necho \"\"\necho \"Checking optional directories...\"\nfor dir in \"${OPTIONAL_DIRS[@]}\"; do\n if [ -d \"$ROLE_ABS_PATH/$dir\" ]; then\n echo -e \" ${COLOR_GREEN}✓${COLOR_RESET} $dir/\"\n fi\ndone\n\necho \"\"\n\n# Check for main.yml files\necho \"Checking main.yml files...\"\nfor dir in tasks defaults handlers vars meta; do\n if [ -d \"$ROLE_ABS_PATH/$dir\" ]; then\n if [ -f \"$ROLE_ABS_PATH/$dir/main.yml\" ] || [ -f \"$ROLE_ABS_PATH/$dir/main.yaml\" ]; then\n echo -e \" ${COLOR_GREEN}✓${COLOR_RESET} $dir/main.yml exists\"\n else\n echo -e \" ${COLOR_YELLOW}⚠${COLOR_RESET} $dir/main.yml not found\"\n WARNINGS=$((WARNINGS + 1))\n fi\n fi\ndone\n\necho \"\"\n\n# Stage 2: YAML Syntax Check\necho -e \"${COLOR_BLUE}[2/5] YAML Syntax Check (yamllint)${COLOR_RESET}\"\necho \"-----------------------------------\"\n\n# yamllint - prefer system, fallback to venv\nYAMLLINT_CMD=\"\"\nif command -v yamllint >/dev/null 2>&1; then\n YAMLLINT_CMD=\"yamllint\"\nelif [ -n \"$TEMP_VENV\" ] && [ -f \"$TEMP_VENV/bin/yamllint\" ]; then\n YAMLLINT_CMD=\"$TEMP_VENV/bin/yamllint\"\nfi\n\nif [ -n \"$YAMLLINT_CMD\" ]; then\n # Check if custom config exists\n if [ -f \"$SKILL_DIR/assets/.yamllint\" ]; then\n YAMLLINT_CONFIG=\"-c $SKILL_DIR/assets/.yamllint\"\n else\n YAMLLINT_CONFIG=\"\"\n fi\n\n # Find all YAML files in the role\n YAML_FILES=$(find \"$ROLE_ABS_PATH\" -type f \\( -name \"*.yml\" -o -name \"*.yaml\" \\) ! -path \"*/.git/*\" ! -path \"*/molecule/*\" 2>/dev/null || true)\n\n if [ -n \"$YAML_FILES\" ]; then\n YAML_ERRORS=0\n for file in $YAML_FILES; do\n if ! $YAMLLINT_CMD $YAMLLINT_CONFIG \"$file\" 2>&1 | grep -v \"warning found forbidden document start\"; then\n YAML_ERRORS=$((YAML_ERRORS + 1))\n fi\n done\n\n if [ $YAML_ERRORS -eq 0 ]; then\n echo -e \"${COLOR_GREEN}✓ YAML syntax check passed${COLOR_RESET}\"\n else\n echo -e \"${COLOR_RED}✗ YAML syntax check failed on $YAML_ERRORS file(s)${COLOR_RESET}\"\n ERRORS=$((ERRORS + 1))\n fi\n else\n echo -e \"${COLOR_YELLOW}⚠ No YAML files found in role${COLOR_RESET}\"\n fi\nelse\n echo -e \"${COLOR_YELLOW}⚠ yamllint not available - skipping YAML syntax check${COLOR_RESET}\"\n WARNINGS=$((WARNINGS + 1))\nfi\n\necho \"\"\n\n# Stage 3: Ansible Syntax Check\necho -e \"${COLOR_BLUE}[3/5] Ansible Syntax Check${COLOR_RESET}\"\necho \"-----------------------------------\"\n\nif [ $USE_SYSTEM_ANSIBLE -eq 1 ] || [ -n \"$TEMP_VENV\" ]; then\n # Create a temporary test playbook\n TEMP_PLAYBOOK=$(mktemp)\n cat > \"$TEMP_PLAYBOOK\" \u003c\u003cEOF\n---\n- hosts: localhost\n gather_facts: no\n roles:\n - role: test_role\nEOF\n\n ROLE_NAME=$(basename \"$ROLE_ABS_PATH\")\n\n # Create temporary roles directory\n TEMP_DIR=$(mktemp -d)\n ln -s \"$ROLE_ABS_PATH\" \"$TEMP_DIR/test_role\"\n\n # Run syntax check\n if ANSIBLE_ROLES_PATH=\"$TEMP_DIR\" run_ansible_playbook --syntax-check \"$TEMP_PLAYBOOK\" >/dev/null 2>&1; then\n echo -e \"${COLOR_GREEN}✓ Ansible syntax check passed${COLOR_RESET}\"\n else\n echo -e \"${COLOR_RED}✗ Ansible syntax check failed${COLOR_RESET}\"\n echo \" Run manually for details:\"\n echo \" ANSIBLE_ROLES_PATH=$TEMP_DIR ansible-playbook --syntax-check $TEMP_PLAYBOOK\"\n ERRORS=$((ERRORS + 1))\n fi\n\n # Cleanup\n rm -f \"$TEMP_PLAYBOOK\"\n rm -rf \"$TEMP_DIR\"\nelse\n echo -e \"${COLOR_RED}✗ ansible-playbook not available${COLOR_RESET}\"\n ERRORS=$((ERRORS + 1))\nfi\n\necho \"\"\n\n# Stage 4: Ansible Lint\necho -e \"${COLOR_BLUE}[4/5] Ansible Lint (ansible-lint)${COLOR_RESET}\"\necho \"-----------------------------------\"\n\nif [ $USE_SYSTEM_ANSIBLE_LINT -eq 1 ] || [ -n \"$TEMP_VENV\" ]; then\n # Check if custom config exists\n if [ -f \"$SKILL_DIR/assets/.ansible-lint\" ]; then\n ANSIBLE_LINT_CONFIG=\"-c $SKILL_DIR/assets/.ansible-lint\"\n else\n ANSIBLE_LINT_CONFIG=\"\"\n fi\n\n # Run ansible-lint on the role\n if run_ansible_lint $ANSIBLE_LINT_CONFIG \"$ROLE_ABS_PATH\" 2>&1; then\n echo -e \"${COLOR_GREEN}✓ Ansible lint check passed${COLOR_RESET}\"\n else\n LINT_EXIT=$?\n if [ $LINT_EXIT -eq 2 ]; then\n echo -e \"${COLOR_RED}✗ Ansible lint found errors${COLOR_RESET}\"\n ERRORS=$((ERRORS + 1))\n else\n echo -e \"${COLOR_YELLOW}⚠ Ansible lint found warnings${COLOR_RESET}\"\n WARNINGS=$((WARNINGS + 1))\n fi\n fi\nelse\n echo -e \"${COLOR_YELLOW}⚠ ansible-lint not available - skipping lint check${COLOR_RESET}\"\n WARNINGS=$((WARNINGS + 1))\nfi\n\necho \"\"\n\n# Stage 5: Molecule Check (if configured)\necho -e \"${COLOR_BLUE}[5/5] Molecule Configuration Check${COLOR_RESET}\"\necho \"-----------------------------------\"\n\nif [ -d \"$ROLE_ABS_PATH/molecule\" ]; then\n echo -e \"${COLOR_GREEN}✓ Molecule directory found${COLOR_RESET}\"\n\n # List scenarios\n SCENARIOS=$(ls -1 \"$ROLE_ABS_PATH/molecule\" 2>/dev/null || true)\n if [ -n \"$SCENARIOS\" ]; then\n echo \" Available scenarios:\"\n for scenario in $SCENARIOS; do\n echo \" - $scenario\"\n done\n\n echo \"\"\n echo \" Run tests with: bash scripts/test_role.sh $ROLE_ABS_PATH\"\n echo \" (Molecule will be automatically installed in a temporary venv if needed)\"\n fi\nelse\n echo -e \"${COLOR_YELLOW}⚠ Molecule not configured for this role${COLOR_RESET}\"\n echo \" Initialize with: cd $ROLE_ABS_PATH && molecule init scenario\"\nfi\n\necho \"\"\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}Validation Summary${COLOR_RESET}\"\necho -e \"${COLOR_BLUE}========================================${COLOR_RESET}\"\n\nif [ $ERRORS -eq 0 ] && [ $WARNINGS -eq 0 ]; then\n echo -e \"${COLOR_GREEN}✓ All checks passed successfully!${COLOR_RESET}\"\n echo \"\"\n echo \"For best practices, see: $SKILL_DIR/references/best_practices.md\"\n if [ -n \"$TEMP_VENV\" ]; then\n echo \"\"\n echo \"Note: Some tools were installed in a temporary environment.\"\n echo \"To install permanently: pip install ansible ansible-lint yamllint\"\n fi\n exit 0\nelif [ $ERRORS -eq 0 ]; then\n echo -e \"${COLOR_YELLOW}⚠ Validation completed with $WARNINGS warning(s)${COLOR_RESET}\"\n echo \"\"\n echo \"For best practices, see: $SKILL_DIR/references/best_practices.md\"\n if [ -n \"$TEMP_VENV\" ]; then\n echo \"\"\n echo \"Note: Some tools were installed in a temporary environment.\"\n echo \"To install permanently: pip install ansible ansible-lint yamllint\"\n fi\n exit 0\nelse\n echo -e \"${COLOR_RED}✗ Validation failed with $ERRORS error(s) and $WARNINGS warning(s)${COLOR_RESET}\"\n echo \"\"\n echo \"For troubleshooting, see: $SKILL_DIR/references/common_errors.md\"\n echo \"For best practices, see: $SKILL_DIR/references/best_practices.md\"\n exit 1\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":10978,"content_sha256":"26bd3db59a0d4b687792eb9c4c2e172ba2291201b916d6a87017678b4fb6c508"},{"filename":"test/inventory/localhost-nested.yml","content":"---\nall:\n children:\n local:\n hosts:\n localhost:\n ansible_user: tester\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":95,"content_sha256":"f05b5a73b57b5c6c304c33f69a03a05a0c2e018ea9111f809b10e0cf29123f7a"},{"filename":"test/playbooks/bad-playbook.yml","content":"---\n# Example of a poorly-written Ansible playbook\n# This playbook has multiple issues that should be caught by validation\n\n- name: Setup server\n hosts: all\n become: yes\n vars:\n password: \"hardcoded_password_123\" # SECURITY ISSUE: Hardcoded password\n\n tasks:\n # ISSUE: Task without name\n - apt:\n name: nginx\n\n # ISSUE: Using shell instead of module\n - name: Create directory\n shell: mkdir -p /opt/myapp\n\n # ISSUE: No changed_when for command\n - name: Check service\n command: systemctl status nginx\n\n # ISSUE: Unquoted variable in shell (command injection risk)\n - name: Process file\n shell: cat {{ user_file }}\n\n # ISSUE: Missing mode on file\n - name: Create config\n copy:\n src: config.yml\n dest: /etc/app/config.yml\n\n # ISSUE: Using deprecated with_items\n - name: Install packages\n apt:\n name: \"{{ item }}\"\n with_items:\n - git\n - curl\n\n # ISSUE: Unnecessary become on read-only task\n - name: Read file\n slurp:\n src: /etc/hostname\n become: yes\n\n # ISSUE: Handler name mismatch\n - name: Update config\n template:\n src: app.conf.j2\n dest: /etc/app.conf\n notify: restart service # Handler doesn't exist\n\n # ISSUE: No when condition for OS-specific package manager\n - name: Install package\n yum:\n name: httpd\n\n # ISSUE: Overly permissive file permissions\n - name: Create script\n copy:\n src: script.sh\n dest: /usr/local/bin/script.sh\n mode: '0777' # World writable!\n\n # ISSUE: Disabled SSL verification\n - name: Download file\n get_url:\n url: https://example.com/file.tar.gz\n dest: /tmp/file.tar.gz\n validate_certs: no # Security issue\n\n # ISSUE: Using short module name instead of FQCN\n - name: Install package\n package:\n name: vim\n\n # ISSUE: Handler name doesn't match notification\n handlers:\n - name: restart nginx\n systemd:\n name: nginx\n state: restarted\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":2048,"content_sha256":"6e20dee21f1aa5a847946ea00530fec76d7980b8fdf732c5807589f3a6befda1"},{"filename":"test/playbooks/good-playbook.yml","content":"---\n# Example of a well-written Ansible playbook\n# This playbook follows best practices and should pass all validation\n\n- name: Configure web server\n hosts: webservers\n become: true\n vars:\n nginx_port: 80\n nginx_user: www-data\n app_version: \"1.0.0\"\n\n tasks:\n - name: Update apt cache\n ansible.builtin.apt:\n update_cache: true\n cache_valid_time: 3600\n when: ansible_os_family == \"Debian\"\n tags:\n - packages\n\n - name: Install nginx web server\n ansible.builtin.apt:\n name: nginx\n state: present\n when: ansible_os_family == \"Debian\"\n tags:\n - packages\n - nginx\n\n - name: Create nginx configuration directory\n ansible.builtin.file:\n path: /etc/nginx/sites-available\n state: directory\n owner: root\n group: root\n mode: '0755'\n tags:\n - configuration\n - nginx\n\n - name: Configure nginx virtual host\n ansible.builtin.template:\n src: templates/vhost.conf.j2\n dest: /etc/nginx/sites-available/default\n owner: root\n group: root\n mode: '0644'\n validate: nginx -t -c %s\n notify:\n - Reload nginx\n tags:\n - configuration\n - nginx\n\n - name: Enable nginx site\n ansible.builtin.file:\n src: /etc/nginx/sites-available/default\n dest: /etc/nginx/sites-enabled/default\n state: link\n notify:\n - Reload nginx\n tags:\n - configuration\n - nginx\n\n - name: Ensure nginx is started and enabled\n ansible.builtin.systemd:\n name: nginx\n state: started\n enabled: true\n tags:\n - services\n - nginx\n\n - name: Configure firewall to allow HTTP\n ansible.builtin.iptables:\n chain: INPUT\n protocol: tcp\n destination_port: \"{{ nginx_port }}\"\n jump: ACCEPT\n comment: Allow HTTP traffic\n when: ansible_os_family == \"Debian\"\n tags:\n - security\n - firewall\n\n handlers:\n - name: Reload nginx\n ansible.builtin.systemd:\n name: nginx\n state: reloaded\n\n - name: Restart nginx\n ansible.builtin.systemd:\n name: nginx\n state: restarted\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":2241,"content_sha256":"8fba36b1c54f8af7c91e3d5a7a9f4e099b4fd887b5821497874de9088eeb0cba"},{"filename":"test/playbooks/regression-inline-fqcn.yml","content":"---\n- name: Inline module style regression\n hosts: localhost\n gather_facts: false\n tasks:\n - name: Inline short module should be detected\n shell: echo \"hello\"\n\n - name: Inline FQCN should not be detected\n ansible.builtin.shell: echo \"hello\"\n\n - name: Parameter keys must not be treated as module usage\n ansible.builtin.user:\n name: appuser\n group: staff\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":395,"content_sha256":"c69147f2e4e39f9f2bddac5a9017e9f48e6edfb889ef048e714a79ae66fc6648"},{"filename":"test/playbooks/regression-mixed-import.yml","content":"---\n- import_playbook: bootstrap.yml\n\n- name: Mixed import + play regression\n hosts: all\n gather_facts: false\n tasks:\n - name: Use short module form inside a real play\n shell: echo \"hello\"\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":200,"content_sha256":"2ac283a481e72dbcd03c6d16c22a2a0de2f8d47825dfe9b651ac9119a89335b4"},{"filename":"test/README.md","content":"# Ansible Testing\n\nThis directory contains test materials for validating the ansible-validator skill, including both playbooks and roles.\n\n## Directory Structure\n\n```\ntest/\n├── README.md # This file\n├── test_regressions.sh # Regression tests for validator scripts\n├── inventory/ # Inventory fixtures for validation tests\n│ └── localhost-nested.yml\n├── playbooks/ # Example playbooks for testing\n│ ├── good-playbook.yml # Well-written playbook\n│ ├── bad-playbook.yml # Playbook with issues\n│ ├── regression-inline-fqcn.yml\n│ └── regression-mixed-import.yml\n└── roles/ # Test roles\n └── geerlingguy.mysql/ # Production-quality MySQL role\n```\n\n## Test Playbooks\n\n### good-playbook.yml\n\nA well-written Ansible playbook that follows best practices:\n- All tasks are named\n- Uses appropriate modules instead of shell/command\n- Proper file permissions\n- No hardcoded secrets\n- Uses handlers correctly\n- Implements tags for granular execution\n- OS-specific tasks have conditionals\n\n**Expected validation results:** Should pass all checks (yamllint, ansible-lint, syntax check)\n\n### bad-playbook.yml\n\nA poorly-written playbook with multiple issues:\n- Hardcoded password (security issue)\n- Tasks without names\n- Using shell instead of modules\n- Missing changed_when for commands\n- Command injection risk (unquoted variables)\n- Missing file permissions\n- Deprecated with_items\n- Handler name mismatch\n- Overly permissive file permissions (777)\n- Disabled SSL verification\n- Missing OS conditionals\n\n**Expected validation results:** Should fail multiple checks and report numerous issues\n\n## Test Role: geerlingguy.mysql\n\nThis is a well-maintained, production-quality Ansible role by Jeff Geerling for installing and configuring MySQL.\n\n**Source:** https://github.com/geerlingguy/ansible-role-mysql\n\n**Features:**\n- Complete role structure (tasks, defaults, handlers, meta, templates, vars)\n- Molecule testing configured\n- Multi-platform support (Debian, RedHat, Ubuntu, etc.)\n- Comprehensive variable management\n- Well-documented\n\nThis role serves as an excellent example for:\n- Proper role structure\n- Best practices implementation\n- Molecule testing setup\n- Multi-OS compatibility patterns\n\n## Testing Commands\n\n### Playbook Testing\n\n```bash\n# Validate good playbook (should pass)\nbash ../scripts/validate_playbook.sh playbooks/good-playbook.yml\n\n# Validate bad playbook (should fail with multiple errors)\nbash ../scripts/validate_playbook.sh playbooks/bad-playbook.yml\n\n# Extract modules from playbook\nbash ../scripts/extract_ansible_info_wrapper.sh playbooks/good-playbook.yml\nbash ../scripts/extract_ansible_info_wrapper.sh playbooks/bad-playbook.yml\n\n# Individual validation steps\nyamllint -c ../assets/.yamllint playbooks/good-playbook.yml\nansible-playbook --syntax-check playbooks/good-playbook.yml # if ansible installed\nansible-lint -c ../assets/.ansible-lint playbooks/good-playbook.yml # if ansible-lint installed\n```\n\n### Script Regression Testing\n\n```bash\n# Run targeted regressions for ansible-validator scripts\nbash test_regressions.sh\n```\n\n### Role Testing\n\n```bash\n# Preflight tool/runtime readiness\nbash ../scripts/setup_tools.sh\n\n# Comprehensive role validation\nbash ../scripts/validate_role.sh roles/geerlingguy.mysql\n\n# This checks:\n# - Role directory structure\n# - YAML syntax (yamllint)\n# - Ansible syntax (if ansible is installed)\n# - Ansible lint (if ansible-lint is installed)\n# - Molecule configuration\n```\n\n### Extract Module Information\n\n```bash\n# Extract modules from role\nbash ../scripts/extract_ansible_info_wrapper.sh roles/geerlingguy.mysql\n\n# Extract modules from playbook\nbash ../scripts/extract_ansible_info_wrapper.sh playbooks/good-playbook.yml\n```\n\n### Run Molecule Tests\n\n```bash\n# Run full molecule test suite\n# Note: Molecule will be automatically installed in a temporary venv if not already installed\nbash ../scripts/test_role.sh roles/geerlingguy.mysql\n\n# Or run molecule directly (if installed)\ncd roles/geerlingguy.mysql\nmolecule test\n```\n\n**Note:** The `test_role.sh` script automatically handles molecule installation. If molecule is not found on your system, the script will:\n1. Create a temporary Python virtual environment\n2. Install molecule, ansible-core, ansible-lint, and yamllint\n3. Run the full test suite\n4. Clean up the temporary environment automatically\n\nNo permanent installation required.\n\nExit codes:\n- `0` = Molecule tests completed successfully\n- `1` = Molecule tests ran but role/test stages failed\n- `2` = Molecule blocked by runtime/dependency environment (for example Docker/Podman unavailable)\n\n## Expected Validation Results\n\n### Structure Check\n- ✅ All required directories present (tasks/)\n- ✅ All recommended directories present (defaults/, handlers/, meta/, templates/, vars/)\n- ✅ Molecule directory configured\n- ✅ Main YAML files exist\n\n### YAML Syntax\n- ✅ All YAML files are syntactically correct\n- ⚠️ Some warnings about line length (acceptable)\n- ⚠️ Minor trailing spaces in CI workflow files\n\n### Ansible Lint\n- Should pass most checks (role follows best practices)\n- May have minor warnings about formatting\n\n### Molecule\n- Configured with default scenario\n- Tests across multiple platforms (Ubuntu, Debian, Rocky Linux)\n- Includes idempotency checks\n\n## Adding More Test Roles\n\nTo add additional test roles for validation:\n\n```bash\n# Download from Ansible Galaxy\ncd test/roles\nansible-galaxy role install namespace.rolename -p .\n\n# Or clone from GitHub\ngit clone https://github.com/author/ansible-role-name.git author.rolename\n```\n\n### Recommended Test Roles\n\nPopular, well-maintained roles for testing:\n\n```bash\n# Web servers\ngit clone https://github.com/geerlingguy/ansible-role-apache.git geerlingguy.apache\ngit clone https://github.com/geerlingguy/ansible-role-nginx.git geerlingguy.nginx\n\n# Databases\ngit clone https://github.com/geerlingguy/ansible-role-postgresql.git geerlingguy.postgresql\ngit clone https://github.com/geerlingguy/ansible-role-redis.git geerlingguy.redis\n\n# Languages/Runtimes\ngit clone https://github.com/geerlingguy/ansible-role-php.git geerlingguy.php\ngit clone https://github.com/geerlingguy/ansible-role-nodejs.git geerlingguy.nodejs\n\n# DevOps tools\ngit clone https://github.com/geerlingguy/ansible-role-docker.git geerlingguy.docker\ngit clone https://github.com/geerlingguy/ansible-role-kubernetes.git geerlingguy.kubernetes\n```\n\n## Integration Testing\n\n### Test the Full Validation Pipeline\n\n```bash\n#!/bin/bash\n# test_all_roles.sh - Validate all roles in test directory\n\nfor role in roles/*; do\n if [ -d \"$role\" ]; then\n echo \"Validating $(basename $role)...\"\n bash ../scripts/validate_role.sh \"$role\"\n echo \"\"\n fi\ndone\n```\n\n### Test Module Extraction\n\n```bash\n#!/bin/bash\n# extract_all_modules.sh - Extract modules from all roles\n\nfor role in roles/*; do\n if [ -d \"$role\" ]; then\n echo \"=== $(basename $role) ===\"\n bash ../scripts/extract_ansible_info_wrapper.sh \"$role\"\n echo \"\"\n fi\ndone\n```\n\n## Common Role Issues to Test\n\nThe validation scripts can detect:\n\n1. **Structure Issues:**\n - Missing required directories (tasks/)\n - Missing main.yml files\n - Incorrect file naming\n\n2. **Syntax Issues:**\n - Invalid YAML syntax\n - Indentation problems\n - Missing colons or quotes\n\n3. **Best Practice Violations:**\n - Tasks without names\n - Hard-coded values instead of variables\n - Missing handlers\n - Improper use of command vs. shell\n\n4. **Security Issues:**\n - Hard-coded secrets\n - Overly permissive file modes\n - Missing no_log on sensitive tasks\n\n5. **Documentation Issues:**\n - Missing README.md\n - Missing role metadata (meta/main.yml)\n - Missing variable documentation\n\n## Molecule Testing Details\n\nThe geerlingguy.mysql role includes molecule configuration:\n\n```\nmolecule/\n└── default/\n ├── molecule.yml # Molecule configuration\n ├── converge.yml # Playbook to test the role\n └── verify.yml # Verification tests\n```\n\n### Running Molecule Tests\n\n```bash\ncd roles/geerlingguy.mysql\n\n# Full test sequence\nmolecule test\n\n# Individual stages\nmolecule create # Create test instances\nmolecule converge # Run the role\nmolecule verify # Run verification tests\nmolecule destroy # Clean up\n\n# Debug mode\nmolecule converge\nmolecule login # SSH into test instance\n```\n\n## Creating Your Own Test Role\n\nTo create a minimal test role:\n\n```bash\nmkdir -p test/roles/mytest/tasks\ncat > test/roles/mytest/tasks/main.yml \u003c\u003cEOF\n---\n- name: Install package\n apt:\n name: vim\n state: present\n when: ansible_os_family == \"Debian\"\nEOF\n\n# Validate it\nbash scripts/validate_role.sh test/roles/mytest\n```\n\n## CI/CD Integration\n\nThese test roles can be used in CI/CD pipelines:\n\n```yaml\n# .github/workflows/validate-roles.yml\nname: Validate Ansible Roles\n\non: [push, pull_request]\n\njobs:\n validate:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v2\n\n - name: Install dependencies\n run: |\n pip install ansible ansible-lint yamllint\n\n - name: Validate all roles\n run: |\n for role in test/roles/*; do\n bash scripts/validate_role.sh \"$role\"\n done\n```\n\n## Troubleshooting\n\n### Role validation fails with \"ansible-playbook not found\"\n\nInstall Ansible:\n```bash\npip install ansible\n```\n\n### Molecule tests fail with \"docker not found\"\n\nThe test_role.sh script will automatically install molecule in a temporary venv, but you still need Docker installed for molecule to create test containers:\n\n```bash\n# macOS\nbrew install docker\n\n# Start Docker Desktop\nopen -a Docker\n\n# Linux\nsudo apt-get install docker.io # Debian/Ubuntu\nsudo yum install docker # RHEL/CentOS\n```\n\nNote: Molecule itself doesn't need to be installed - the script handles that automatically.\n\n### YAML lint warnings about line length\n\nThis is acceptable for readability. Adjust `.yamllint` if needed:\n```yaml\nrules:\n line-length:\n max: 200 # Increase limit\n level: warning\n```\n\n## Resources\n\n- [Ansible Galaxy](https://galaxy.ansible.com/) - Find more roles to test\n- [Molecule Documentation](https://molecule.readthedocs.io/) - Learn about role testing\n- [Jeff Geerling's Roles](https://github.com/geerlingguy?tab=repositories&q=ansible-role) - High-quality example roles\n- [Ansible Best Practices](https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":10576,"content_sha256":"0db4d52f93660eeb08fc1a50e8ff223a0a1b0f1fe4c16e52ce2925c9df2be9cb"},{"filename":"test/roles/geerlingguy.mysql/defaults/main.yml","content":"---\n# Set this to the user ansible is logging in as - should have root\n# or sudo access\nmysql_user_home: /root\nmysql_user_name: root\nmysql_user_password: root\n\n# The default root user installed by mysql - almost always root\nmysql_root_home: /root\nmysql_root_username: root\nmysql_root_password: root\n\n# Set this to `true` to forcibly update the root password.\nmysql_root_password_update: false\nmysql_user_password_update: false\n\nmysql_enabled_on_startup: true\n\n# Whether my.cnf should be updated on every run.\noverwrite_global_mycnf: true\n\n# The following variables have a default value depending on operating system.\n# mysql_config_file: /etc/my.cnf\n# mysql_config_include_dir: /etc/my.cnf.d\n\n# Whether to copy root user's config file with credentials to their homedir\nmysql_copy_root_user_mycnf: true\n\n# Pass in a comma-separated list of repos to use (e.g. \"remi,epel\"). Used only\n# for RedHat systems (and derivatives).\nmysql_enablerepo: \"\"\n\n# Define a custom list of packages to install; if none provided, the default\n# package list from vars/[OS-family].yml will be used.\n# mysql_packages:\n# - mysql\n# - mysql-server\n# - MySQL-python\n\nmysql_python_package_debian: python3-mysqldb\n\n# MySQL connection settings.\nmysql_port: \"3306\"\nmysql_bind_address: '0.0.0.0'\nmysql_skip_name_resolve: false\nmysql_datadir: /var/lib/mysql\nmysql_sql_mode: ~\n# The following variables have a default value depending on operating system.\n# mysql_pid_file: /var/run/mysqld/mysqld.pid\n# mysql_socket: /var/lib/mysql/mysql.sock\n\n# Log file settings.\nmysql_log_file_group: mysql\n\n# Slow query log settings.\nmysql_slow_query_log_enabled: false\nmysql_slow_query_time: \"2\"\n# The following variable has a default value depending on operating system.\n# mysql_slow_query_log_file: /var/log/mysql-slow.log\n\n# Memory settings (default values optimized ~512MB RAM).\nmysql_key_buffer_size: \"256M\"\nmysql_max_allowed_packet: \"64M\"\nmysql_table_open_cache: \"256\"\nmysql_sort_buffer_size: \"1M\"\nmysql_read_buffer_size: \"1M\"\nmysql_read_rnd_buffer_size: \"4M\"\nmysql_myisam_sort_buffer_size: \"64M\"\nmysql_thread_cache_size: \"8\"\n\n# Query caching not available on Mysql version ≥ 8.0\n# On MariaDB, behavior changed on version ≥ 10.1.7:\n# * even if query cache usage (`query_cache_type`) is configured, it depends on the configured cache size\n# (`query_cache_size`), cf. https://mariadb.com/kb/en/server-system-variables/#query_cache_type:\n# * if you set size to 0, query caching is disabled\n# * if you set size to non-zero, query caching is enabled\n# * we are unsure of `query_cache_type = 2/DEMAND` behavior\n# * `query_cache_size` and `query_cache_limit` can still be configured and have the same semantics and default values as\n# their previous MySQL counterparts\nmysql_query_cache_type: \"0\"\nmysql_query_cache_size: \"16M\"\nmysql_query_cache_limit: \"1M\"\n\nmysql_max_connections: \"151\"\nmysql_tmp_table_size: \"16M\"\nmysql_max_heap_table_size: \"16M\"\nmysql_group_concat_max_len: \"1024\"\nmysql_join_buffer_size: \"262144\"\n\n# Other settings.\nmysql_lower_case_table_names: \"0\"\nmysql_wait_timeout: \"28800\"\nmysql_event_scheduler_state: \"OFF\"\n\n# InnoDB settings.\nmysql_innodb_file_per_table: \"1\"\n# Set .._buffer_pool_size up to 80% of RAM but beware of setting too high.\nmysql_innodb_buffer_pool_size: \"256M\"\n# Set .._log_file_size to 25% of buffer pool size.\nmysql_innodb_log_file_size: \"64M\"\nmysql_innodb_log_buffer_size: \"8M\"\nmysql_innodb_flush_log_at_trx_commit: \"1\"\nmysql_innodb_lock_wait_timeout: \"50\"\n\n# These settings require MySQL > 5.5.\nmysql_innodb_large_prefix: \"1\"\nmysql_innodb_file_format: \"barracuda\"\n\n# mysqldump settings.\nmysql_mysqldump_max_allowed_packet: \"64M\"\n\n# Logging settings.\nmysql_log: \"\"\n# The following variables have a default value depending on operating system.\n# mysql_log_error: /var/log/mysql/mysql.err\n# mysql_syslog_tag: mysql\n\nmysql_config_include_files: []\n# - src: path/relative/to/playbook/file.cnf\n# - { src: path/relative/to/playbook/anotherfile.cnf, force: true }\n\n# Databases.\nmysql_databases: []\n# - name: example\n# collation: utf8_general_ci\n# encoding: utf8\n# replicate: 1\n\n# Users.\nmysql_users: []\n# - name: example\n# host: 127.0.0.1\n# password: secret\n# priv: *.*:USAGE\n\nmysql_disable_log_bin: false\n\n# Replication settings (replication is only enabled if master/user have values).\nmysql_server_id: \"1\"\nmysql_max_binlog_size: \"100M\"\nmysql_binlog_format: \"ROW\"\nmysql_expire_logs_days: \"10\"\nmysql_replication_role: ''\nmysql_replication_master: ''\nmysql_replication_master_inventory_host: \"{{ mysql_replication_master }}\"\n\n# Same keys as `mysql_users` above.\nmysql_replication_user: []\n\nmysql_hide_passwords: false\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":4650,"content_sha256":"bafda0dd0a5dedcde945b98778ce4ddd42c2e4f04322ba434d213a0dd8759541"},{"filename":"test/roles/geerlingguy.mysql/handlers/main.yml","content":"---\n- name: restart mysql\n ansible.builtin.service:\n name: \"{{ mysql_daemon }}\"\n state: restarted\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":105,"content_sha256":"b15d030222178b3ed3a2807dd8eda20ea5f47faa1bcebebd45719102da5e908a"},{"filename":"test/roles/geerlingguy.mysql/meta/main.yml","content":"---\ndependencies: []\n\ngalaxy_info:\n role_name: mysql\n author: geerlingguy\n description: MySQL server for RHEL/CentOS and Debian/Ubuntu.\n company: \"Midwestern Mac, LLC\"\n license: \"license (BSD, MIT)\"\n min_ansible_version: 2.12\n platforms:\n - name: Ubuntu\n versions:\n - all\n - name: Debian\n versions:\n - all\n - name: Archlinux\n versions:\n - all\n galaxy_tags:\n - database\n - mysql\n - mariadb\n - db\n - sql\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":470,"content_sha256":"9e9787e4d0642b16b5ed75620a99f4aca7ccd8896f5859f69d92f3117c8bbdfa"},{"filename":"test/roles/geerlingguy.mysql/molecule/default/converge.yml","content":"---\n- name: Converge\n hosts: all\n # become: true\n\n roles:\n - role: geerlingguy.mysql\n\n post_tasks:\n - name: Make sure we can connect to MySQL via Unix socket.\n ansible.builtin.command: \"mysql -u root -proot -e 'show databases;'\"\n changed_when: false\n\n - name: Make sure we can connect to MySQL via TCP on old MySQL version.\n ansible.builtin.command: \"mysql -u root -proot -h 127.0.0.1 -e 'show databases;'\"\n register: result\n changed_when: false\n failed_when:\n - result.rc != 0 and not (\n ansible_system == \"Linux\" and (\n (mysql_cli_version is version('8.0.34', '>=') and mysql_daemon == 'mysql') or\n (mysql_cli_version is version('10.4', '>=') and mysql_daemon == 'mariadb')\n )\n )\n - result.rc == 0 and (\n ansible_system == \"Linux\" and (\n (mysql_cli_version is version('8.0.34', '>=') and mysql_daemon == 'mysql') or\n (mysql_cli_version is version('10.4', '>=') and mysql_daemon == 'mariadb')\n )\n )\n\n - name: Make sure we can connect to MySQL via socket on modern MySQL version.\n ansible.builtin.command: \"mysql -u root -e 'show databases;'\"\n register: result\n changed_when: false\n failed_when:\n - result.rc == 0 and (\n ansible_system == \"Linux\" and (\n (mysql_cli_version is version('8.0.34', '>=') and mysql_daemon == 'mysql') or\n (mysql_cli_version is version('10.4', '>=') and mysql_daemon == 'mariadb')\n )\n )\n - result.rc != 0 and not (\n ansible_system == \"Linux\" and (\n (mysql_cli_version is version('8.0.34', '>=') and mysql_daemon == 'mysql') or\n (mysql_cli_version is version('10.4', '>=') and mysql_daemon == 'mariadb')\n )\n )\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":1858,"content_sha256":"ec384fe778778096785850c8a36be3872ad43eaf45aa4f77e8b3b41a677fc8ce"},{"filename":"test/roles/geerlingguy.mysql/molecule/default/molecule.yml","content":"---\nrole_name_check: 1\ndependency:\n name: galaxy\n options:\n ignore-errors: true\ndriver:\n name: docker\nplatforms:\n - name: instance\n image: \"geerlingguy/docker-${MOLECULE_DISTRO:-rockylinux9}-ansible:latest\"\n command: ${MOLECULE_DOCKER_COMMAND:-\"\"}\n volumes:\n - /sys/fs/cgroup:/sys/fs/cgroup:rw\n cgroupns_mode: host\n privileged: true\n pre_build_image: true\nprovisioner:\n name: ansible\n playbooks:\n converge: ${MOLECULE_PLAYBOOK:-converge.yml}\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":477,"content_sha256":"66f9c32ea85ef2a63eff324de85560c9b2d1f81449fbd10bcb549aa4ce05e829"},{"filename":"test/roles/geerlingguy.mysql/README.md","content":"# Ansible Role: MySQL\n\n[![CI](https://github.com/geerlingguy/ansible-role-mysql/actions/workflows/ci.yml/badge.svg)](https://github.com/geerlingguy/ansible-role-mysql/actions/workflows/ci.yml)\n\nInstalls and configures MySQL or MariaDB server on RHEL/CentOS or Debian/Ubuntu servers.\n\n## Requirements\n\nNo special requirements; note that this role requires root access, so either run it in a playbook with a global `become: true`, or invoke the role in your playbook like:\n\n```yaml\n- hosts: database\n roles:\n - role: geerlingguy.mysql\n become: true\n```\n\n## Role Variables\n\nAvailable variables are listed below, along with default values (see `defaults/main.yml`):\n\n```yaml\nmysql_user_home: /root\nmysql_user_name: root\nmysql_user_password: root\n```\n\nThe home directory inside which Python MySQL settings will be stored, which Ansible will use when connecting to MySQL. This should be the home directory of the user which runs this Ansible role. The `mysql_user_name` and `mysql_user_password` can be set if you are running this role under a non-root user account and want to set a non-root user.\n\n```yaml\nmysql_root_home: /root\nmysql_root_username: root\nmysql_root_password: root\n```\n\nThe MySQL root user account details.\n\n```yaml\nmysql_root_password_update: false\n```\n\nWhether to force update the MySQL root user's password. By default, this role will only change the root user's password when MySQL is first configured. You can force an update by setting this to `true`.\n\n> Note: If you get an error like `ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)` after a failed or interrupted playbook run, this usually means the root password wasn't originally updated to begin with. Try either removing the `.my.cnf` file inside the configured `mysql_user_home` or updating it and setting `password=''` (the insecure default password). Run the playbook again, with `mysql_root_password_update` set to `yes`, and the setup should complete.\n\n> Note: If you get an error like `ERROR 1698 (28000): Access denied for user 'root'@'localhost' (using password: YES)` when trying to log in from the CLI you might need to run as root or sudoer.\n\n```yaml\nmysql_enabled_on_startup: true\n```\n\nWhether MySQL should be enabled on startup.\n\n```yaml\nmysql_config_file: *default value depends on OS*\nmysql_config_include_dir: *default value depends on OS*\n```\n\nThe main my.cnf configuration file and include directory.\n\n```yaml\noverwrite_global_mycnf: true\n```\n\nWhether the global my.cnf should be overwritten each time this role is run. Setting this to `false` tells Ansible to only create the `my.cnf` file if it doesn't exist. This should be left at its default value (`true`) if you'd like to use this role's variables to configure MySQL.\n\n```yaml\nmysql_config_include_files: []\n```\n\nA list of files that should override the default global my.cnf. Each item in the array requires a \"src\" parameter which is a path to a file. An optional \"force\" parameter can force the file to be updated each time ansible runs.\n\n```yaml\nmysql_databases: []\n```\n\nThe MySQL databases to create. A database has the values `name`, `encoding` (defaults to `utf8`), `collation` (defaults to `utf8_general_ci`) and `replicate` (defaults to `1`, only used if replication is configured). The formats of these are the same as in the `mysql_db` module.\n\nYou can also delete a database (or ensure it's not on the server) by setting `state` to `absent` (defaults to `present`).\n\n```yaml\nmysql_users: []\n```\n\nThe MySQL users and their privileges. A user has the values:\n\n - `name`\n - `host` (defaults to `localhost`)\n - `password` (can be plaintext or encrypted—if encrypted, set `encrypted: true`)\n - `encrypted` (defaults to `false`)\n - `priv` (defaults to `*.*:USAGE`)\n - `append_privs` (defaults to `false`)\n - `state` (defaults to `present`)\n - `case_sensitive` (defaults to `false`)\n - `update_password` (defaults to `always`)\n\nThe formats of these are the same as in the `mysql_user` module.\n\n```yaml\nmysql_packages:\n - mysql\n - mysql-server\n```\n\n(OS-specific, RedHat/CentOS defaults listed here) Packages to be installed. In some situations, you may need to add additional packages, like `mysql-devel`.\n\n```yaml\nmysql_enablerepo: \"\"\n```\n\n(RedHat/CentOS only) If you have enabled any additional repositories (might I suggest geerlingguy.repo-epel or geerlingguy.repo-remi), those repositories can be listed under this variable (e.g. `remi,epel`). This can be handy, as an example, if you want to install later versions of MySQL.\n\n```yaml\nmysql_python_package_debian: python3-mysqldb\n```\n\n(Ubuntu/Debian only) If you need to explicitly override the MySQL Python package, you can set it here. Set this to `python-mysqldb` if using older distributions running Python 2.\n\n```yaml\nmysql_port: \"3306\"\nmysql_bind_address: '0.0.0.0'\nmysql_datadir: /var/lib/mysql\nmysql_socket: *default value depends on OS*\nmysql_pid_file: *default value depends on OS*\n```\n\nDefault MySQL connection configuration.\n\n```yaml\nmysql_log_file_group: mysql *adm on Debian*\nmysql_log: \"\"\nmysql_log_error: *default value depends on OS*\nmysql_syslog_tag: *default value depends on OS*\n```yaml\n\nMySQL logging configuration. Setting `mysql_log` (the general query log) or `mysql_log_error` to `syslog` will make MySQL log to syslog using the `mysql_syslog_tag`.\n\n```yaml\nmysql_slow_query_log_enabled: false\nmysql_slow_query_log_file: *default value depends on OS*\nmysql_slow_query_time: 2\n```\n\nSlow query log settings. Note that the log file will be created by this role, but if you're running on a server with SELinux or AppArmor, you may need to add this path to the allowed paths for MySQL, or disable the mysql profile. For example, on Debian/Ubuntu, you can run `sudo ln -s /etc/apparmor.d/usr.sbin.mysqld /etc/apparmor.d/disable/usr.sbin.mysqld && sudo service apparmor restart`.\n\n```yaml\nmysql_key_buffer_size: \"256M\"\nmysql_max_allowed_packet: \"64M\"\nmysql_table_open_cache: \"256\"\n...\n```\n\nThe rest of the settings in `defaults/main.yml` control MySQL's memory usage and some other common settings. The default values are tuned for a server where MySQL can consume 512 MB RAM, so you should consider adjusting them to suit your particular server better.\n\n```yaml\nmysql_disable_log_bin: false\n```\n\nThis variable should be set to `true` if you don't need replication, or otherwise don't need a log of all MySQL's activity. If you leave it at the default value, disk space may be consumed at an alarming rate on highly-utlilized database servers!\n\n```yaml\nmysql_server_id: \"1\"\nmysql_max_binlog_size: \"100M\"\nmysql_binlog_format: \"ROW\"\nmysql_expire_logs_days: \"10\"\nmysql_replication_role: ''\nmysql_replication_master: ''\nmysql_replication_user: {}\n```\n\nReplication settings. Set `mysql_server_id` and `mysql_replication_role` by server (e.g. the master would be ID `1`, with the `mysql_replication_role` of `master`, and the slave would be ID `2`, with the `mysql_replication_role` of `slave`). The `mysql_replication_user` uses the same keys as individual list items in `mysql_users`, and is created on master servers, and used to replicate on all the slaves.\n\n`mysql_replication_master` needs to resolve to an IP or a hostname which is accessable to the Slaves (this could be a `/etc/hosts` injection or some other means), otherwise the slaves cannot communicate to the master.\n\nIf the replication master has different IP addresses where you are running ansible and where the mysql replica is running, you can *optionally* specify a `mysql_replication_master_inventory_host` to access the machine (e.g. you run ansible on your local machine, but the mysql master and replica need to communicate on a different network)\n\n```yaml\nmysql_hide_passwords: false\n```\n\nDo you need to hide tasks' output which contain passwords during the execution ?\n\n### MariaDB usage\n\nThis role works with either MySQL or a compatible version of MariaDB. On RHEL/CentOS 7+, the mariadb database engine was substituted as the default MySQL replacement package. No modifications are necessary though all of the variables still reference 'mysql' instead of mariadb.\n\n#### Ubuntu 14.04+ MariaDB configuration\n\nOn Ubuntu, the package names are named differently, so the `mysql_package` variable needs to be altered. Set the following variables (at a minimum):\n\n mysql_packages:\n - mariadb-client\n - mariadb-server\n mysql_daemon: mariadb\n\n## Dependencies\n\nIf you have `ansible` installed (e.g. `pip3 install ansible`), none.\n\nIf you have only installed `ansible-core`, be sure to require `community.mysql` in your `collections/requirements.yml` or install it manually with `ansible-galaxy collection install community.mysql`.\n\n## Example Playbook\n\n - hosts: db-servers\n become: true\n vars_files:\n - vars/main.yml\n roles:\n - { role: geerlingguy.mysql }\n\n*Inside `vars/main.yml`*:\n\n mysql_root_password: super-secure-password\n mysql_databases:\n - name: example_db\n encoding: latin1\n collation: latin1_general_ci\n mysql_users:\n - name: example_user\n host: \"%\"\n password: similarly-secure-password\n priv: \"example_db.*:ALL\"\n\n## License\n\nMIT / BSD\n\n## Author Information\n\nThis role was created in 2014 by [Jeff Geerling](https://www.jeffgeerling.com/), author of [Ansible for DevOps](https://www.ansiblefordevops.com/).\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":9372,"content_sha256":"55a3f4fcbb5519d4f58e7d7b120730998faa8606767ed5aaa8766f98090a547d"},{"filename":"test/roles/geerlingguy.mysql/tasks/configure.yml","content":"---\n- name: Get MySQL version\n block:\n - name: Run mysql --version\n ansible.builtin.command: '{{ mysql_daemon }} --version'\n register: mysql_cli_version\n changed_when: false\n check_mode: false\n\n - name: Extract MySQL version\n ansible.builtin.set_fact:\n mysql_cli_version: >-\n {{ mysql_cli_version.stdout | regex_search('\\d+\\.\\d+\\.\\d+(-[a-zA-Z]+)?') }}\n\n- name: Copy my.cnf global MySQL configuration.\n ansible.builtin.template:\n src: my.cnf.j2\n dest: \"{{ mysql_config_file }}\"\n owner: root\n group: root\n mode: 0644\n force: \"{{ overwrite_global_mycnf }}\"\n notify: restart mysql\n\n- name: Verify mysql include directory exists.\n ansible.builtin.file:\n path: \"{{ mysql_config_include_dir }}\"\n state: directory\n owner: root\n group: root\n mode: 0755\n when: mysql_config_include_files | length | bool\n\n- name: Copy my.cnf override files into include directory.\n ansible.builtin.template:\n src: \"{{ item.src }}\"\n dest: \"{{ mysql_config_include_dir }}/{{ item.src | basename }}\"\n owner: root\n group: root\n mode: 0644\n force: \"{{ item.force | default(false) }}\"\n with_items: \"{{ mysql_config_include_files }}\"\n notify: restart mysql\n\n- name: Create slow query log file (if configured).\n ansible.builtin.command: \"touch {{ mysql_slow_query_log_file }}\"\n args:\n creates: \"{{ mysql_slow_query_log_file }}\"\n when: mysql_slow_query_log_enabled\n\n- name: Create datadir if it does not exist\n ansible.builtin.file:\n path: \"{{ mysql_datadir }}\"\n state: directory\n owner: mysql\n group: mysql\n mode: 0755\n setype: mysqld_db_t\n\n- name: Set ownership on slow query log file (if configured).\n ansible.builtin.file:\n path: \"{{ mysql_slow_query_log_file }}\"\n state: file\n owner: mysql\n group: \"{{ mysql_log_file_group }}\"\n mode: 0640\n when: mysql_slow_query_log_enabled\n\n- name: Create error log file (if configured).\n ansible.builtin.command: \"touch {{ mysql_log_error }}\"\n args:\n creates: \"{{ mysql_log_error }}\"\n when:\n - ((mysql_log is defined) and (mysql_log | length > 0)) | default(true)\n - ((mysql_log_error is defined) and (mysql_log_error | length > 0)) | default(true)\n tags: ['skip_ansible_galaxy']\n\n- name: Set ownership on error log file (if configured).\n ansible.builtin.file:\n path: \"{{ mysql_log_error }}\"\n state: file\n owner: mysql\n group: \"{{ mysql_log_file_group }}\"\n mode: 0640\n when:\n - ((mysql_log is defined) and (mysql_log | length > 0)) | default(true)\n - ((mysql_log_error is defined) and (mysql_log_error | length > 0)) | default(true)\n tags: ['skip_ansible_galaxy']\n\n- name: Ensure MySQL is started and enabled on boot.\n ansible.builtin.service:\n name: \"{{ mysql_daemon }}\"\n state: started\n enabled: \"{{ mysql_enabled_on_startup }}\"\n register: mysql_service_configuration\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":2871,"content_sha256":"bd31cb268bcb1cc7ddccb41691448d680f01dce1ed26b9a423055c143b9b250d"},{"filename":"test/roles/geerlingguy.mysql/tasks/databases.yml","content":"---\n- name: Ensure MySQL databases are present.\n mysql_db:\n name: \"{{ item.name }}\"\n collation: \"{{ item.collation | default('utf8_general_ci') }}\"\n encoding: \"{{ item.encoding | default('utf8') }}\"\n state: \"{{ item.state | default('present') }}\"\n target: \"{{ item.target | default(omit) }}\"\n with_items: \"{{ mysql_databases }}\"\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":346,"content_sha256":"4bf4523576a46a4e144e8ff2f830186157728bcab0d58bd9f65d6c169c6495c5"},{"filename":"test/roles/geerlingguy.mysql/tasks/main.yml","content":"---\n# Variable configuration.\n- ansible.builtin.include_tasks: variables.yml\n\n# Setup/install tasks.\n- ansible.builtin.include_tasks: setup-RedHat.yml\n when: ansible_os_family == 'RedHat'\n\n- ansible.builtin.include_tasks: setup-Debian.yml\n when: ansible_os_family == 'Debian'\n\n- ansible.builtin.include_tasks: setup-Archlinux.yml\n when: ansible_os_family == 'Archlinux'\n\n- name: Check if MySQL packages were installed.\n ansible.builtin.set_fact:\n mysql_install_packages: \"{{ (rh_mysql_install_packages is defined and rh_mysql_install_packages.changed)\n or (deb_mysql_install_packages is defined and deb_mysql_install_packages.changed)\n or (arch_mysql_install_packages is defined and arch_mysql_install_packages.changed) }}\"\n\n# Configure MySQL.\n- ansible.builtin.include_tasks: configure.yml\n- ansible.builtin.include_tasks: secure-installation.yml\n- ansible.builtin.include_tasks: databases.yml\n- ansible.builtin.include_tasks: users.yml\n- ansible.builtin.include_tasks: replication.yml\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":1004,"content_sha256":"daa31269adfe5b7d5e2f451593b2c50487241a02ebe70b08c26e57d7c750a92c"},{"filename":"test/roles/geerlingguy.mysql/tasks/replication.yml","content":"---\n- name: Ensure replication user exists on master.\n mysql_user:\n name: \"{{ mysql_replication_user.name }}\"\n host: \"{{ mysql_replication_user.host | default('%') }}\"\n password: \"{{ mysql_replication_user.password }}\"\n priv: \"{{ mysql_replication_user.priv | default('*.*:REPLICATION SLAVE,REPLICATION CLIENT') }}\"\n update_password: \"{{ mysql_replication_user.update_password | default('always') }}\"\n state: present\n no_log: \"{{ mysql_hide_passwords }}\"\n when:\n - mysql_replication_role == 'master'\n - mysql_replication_user.name is defined\n - (mysql_replication_master | length) > 0\n tags: ['skip_ansible_galaxy']\n\n- name: Check slave replication status.\n mysql_replication:\n mode: getreplica\n login_user: \"{{ mysql_root_username }}\"\n login_password: \"{{ mysql_root_password }}\"\n no_log: \"{{ mysql_hide_passwords }}\"\n ignore_errors: true\n register: slave\n when:\n - mysql_replication_role == 'slave'\n - (mysql_replication_master | length) > 0\n tags: ['skip_ansible_galaxy']\n\n# https://github.com/ansible/ansible/issues/82264\n- name: Check master replication status.\n mysql_replication:\n mode: getprimary\n delegate_to: \"{{ mysql_replication_master_inventory_host | default(omit, true) }}\"\n register: master\n when:\n - >\n (slave.Is_Slave is defined and not slave.Is_Slave) or\n (slave.Is_Replica is defined and not slave.Is_Replica) or\n (slave.Is_Slave is not defined and slave.Is_Replica is not defined and slave is failed)\n - mysql_replication_role == 'slave'\n - (mysql_replication_master | length) > 0\n tags: ['skip_ansible_galaxy']\n\n- name: Configure replication on the slave.\n mysql_replication:\n mode: changeprimary\n master_host: \"{{ mysql_replication_master }}\"\n master_user: \"{{ mysql_replication_user.name }}\"\n master_password: \"{{ mysql_replication_user.password }}\"\n master_log_file: \"{{ master.File }}\"\n master_log_pos: \"{{ master.Position }}\"\n no_log: \"{{ mysql_hide_passwords }}\"\n ignore_errors: true\n when:\n - >\n (slave.Is_Slave is defined and not slave.Is_Slave) or\n (slave.Is_Replica is defined and not slave.Is_Replica) or\n (slave.Is_Slave is not defined and slave.Is_Replica is not defined and slave is failed)\n - mysql_replication_role == 'slave'\n - mysql_replication_user.name is defined\n - (mysql_replication_master | length) > 0\n\n- name: Start replication.\n mysql_replication:\n mode: startreplica\n when:\n - >\n (slave.Is_Slave is defined and slave.Is_Slave) or\n (slave.Is_Replica is defined and slave.Is_Replica) or\n (slave.Is_Slave is not defined and slave.Is_Replica is not defined and slave is failed)\n - mysql_replication_role == 'slave'\n - (mysql_replication_master | length) > 0\n tags: ['skip_ansible_galaxy']\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":2798,"content_sha256":"51415671db203d6d825cb447d6b27f4424995e4bfb90487570209ab375e2f92e"},{"filename":"test/roles/geerlingguy.mysql/tasks/secure-installation.yml","content":"---\n- name: Ensure default user is present.\n mysql_user:\n name: \"{{ mysql_user_name }}\"\n host: 'localhost'\n password: \"{{ mysql_user_password }}\"\n priv: '*.*:ALL,GRANT'\n state: present\n no_log: \"{{ mysql_hide_passwords }}\"\n when: mysql_user_name != mysql_root_username\n\n# Has to be after the password assignment, for idempotency.\n- name: Copy user-my.cnf file with password credentials.\n ansible.builtin.template:\n src: \"user-my.cnf.j2\"\n dest: \"{{ mysql_user_home }}/.my.cnf\"\n owner: \"{{ mysql_user_name }}\"\n mode: 0600\n no_log: \"{{ mysql_hide_passwords }}\"\n when: >\n mysql_user_name != mysql_root_username\n and (mysql_install_packages | bool or mysql_user_password_update)\n\n- name: Disallow root login remotely\n ansible.builtin.command: '{{ mysql_daemon }} -NBe \"{{ item }}\"'\n with_items:\n - DELETE FROM mysql.user WHERE User='{{ mysql_root_username }}' AND Host NOT IN ('localhost', '127.0.0.1', '::1')\n changed_when: false\n\n- name: Get list of hosts for the root user.\n ansible.builtin.command: >\n {{ mysql_daemon }} -NBe\n \"SELECT Host\n FROM mysql.user\n WHERE User = '{{ mysql_root_username }}'\n ORDER BY (Host='localhost') ASC\"\n register: mysql_root_hosts\n changed_when: false\n check_mode: false\n when: mysql_install_packages | bool or mysql_root_password_update\n\n# Note: We do not use mysql_user for this operation, as it doesn't always update\n# the root password correctly. See: https://goo.gl/MSOejW\n# Set root password for MySQL >= 8.4 and MariaDB ≥ 10.4\n- name: Update MySQL root authentication via socket for localhost (Linux, MySQL ≥ 8.4)\n ansible.builtin.shell: >\n {{ mysql_daemon }} -u root -NBe\n \"ALTER USER '{{ mysql_root_username }}'@'{{ item }}'\n IDENTIFIED {{ (mysql_daemon == 'mariadb') | ternary('VIA unix_socket', 'WITH auth_socket') }}; FLUSH PRIVILEGES;\"\n no_log: \"{{ mysql_hide_passwords }}\"\n with_items: \"{{ mysql_root_hosts.stdout_lines|default([]) }}\"\n when:\n - (mysql_install_packages | bool) or mysql_root_password_update\n - ansible_system == \"Linux\"\n - (mysql_cli_version is version('8.0.34', '>=') and mysql_daemon == 'mysql') or\n (mysql_cli_version is version('10.4', '>=') and mysql_daemon == 'mariadb')\n\n# Note: We do not use mysql_user for this operation, as it doesn't always update\n# the root password correctly. See: https://goo.gl/MSOejW\n# Set root password for 5.7.x. ≤ MySQL \u003c 8.4 and MariaDB ≥ 10.4\n- name: Update MySQL root password for localhost root account (5.7.x ≤ MySQL \u003c 8.4)\n ansible.builtin.shell: >\n {{ mysql_daemon }} -u root -NBe\n \"ALTER USER '{{ mysql_root_username }}'@'{{ item }}'\n IDENTIFIED {{ (mysql_daemon == 'mariadb') | ternary('VIA', 'WITH') }} mysql_native_password\n BY '{{ mysql_root_password }}'; FLUSH PRIVILEGES;\"\n no_log: \"{{ mysql_hide_passwords }}\"\n with_items: \"{{ mysql_root_hosts.stdout_lines|default([]) }}\"\n when:\n - (mysql_install_packages | bool) or mysql_root_password_update\n - (\n mysql_daemon == 'mysql' and (\n mysql_cli_version is version('5.7', '>=') and\n mysql_cli_version is version('8.4', '\u003c')\n )\n ) or\n (mysql_daemon == 'mariadb' and mysql_cli_version is version('10.4', '\u003c'))\n\n# Set root password for MySQL \u003c 5.7.x.\n- name: Update MySQL root password for localhost root account (\u003c 5.7.x).\n ansible.builtin.shell: >\n {{ mysql_daemon }} -NBe\n 'SET PASSWORD FOR \"{{ mysql_root_username }}\"@\"{{ item }}\" = PASSWORD(\"{{ mysql_root_password }}\"); FLUSH PRIVILEGES;'\n no_log: \"{{ mysql_hide_passwords }}\"\n with_items: \"{{ mysql_root_hosts.stdout_lines|default([]) }}\"\n when:\n - (mysql_install_packages | bool) or mysql_root_password_update\n - (mysql_daemon == 'mysql' and mysql_cli_version is version('5.7', '\u003c'))\n\n# Has to be after the root password assignment, for idempotency.\n- name: Copy .my.cnf file with root password credentials.\n ansible.builtin.template:\n src: \"root-my.cnf.j2\"\n dest: \"{{ mysql_root_home }}/.my.cnf\"\n owner: root\n group: root\n mode: 0600\n no_log: \"{{ mysql_hide_passwords }}\"\n when:\n - mysql_install_packages | bool or mysql_root_password_update\n - mysql_copy_root_user_mycnf\n\n- name: Get list of hosts for the anonymous user.\n ansible.builtin.command: >\n {{ mysql_daemon }} -NBe \"SELECT Host FROM mysql.user WHERE User = ''\"\n register: mysql_anonymous_hosts\n changed_when: false\n check_mode: false\n\n- name: Remove anonymous MySQL users.\n mysql_user:\n name: \"\"\n host: \"{{ item }}\"\n state: absent\n with_items: \"{{ mysql_anonymous_hosts.stdout_lines|default([]) }}\"\n\n- name: Remove MySQL test database.\n mysql_db:\n name: 'test'\n state: absent\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":4684,"content_sha256":"397f7dffcfdd5313a6862aec939fd988fc3a119f6b94bbbf81cd5e3a6d610cc0"},{"filename":"test/roles/geerlingguy.mysql/tasks/setup-Archlinux.yml","content":"---\n- name: Ensure MySQL Python libraries are installed.\n community.general.pacman:\n name: mysql-python\n state: present\n\n- name: Ensure MySQL packages are installed.\n community.general.pacman:\n name: \"{{ mysql_packages }}\"\n state: present\n register: arch_mysql_install_packages\n\n- name: Run mysql_install_db if MySQL packages were changed.\n ansible.builtin.command: mysql_install_db --user=mysql --basedir=/usr --datadir=/var/lib/mysql\n when: arch_mysql_install_packages.changed\n tags: ['skip_ansible_lint']\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":526,"content_sha256":"e7ec856b2080e85b7010c91cf7a1b2589c597b90b3f33d723d1175e51b4fff0d"},{"filename":"test/roles/geerlingguy.mysql/tasks/setup-Debian.yml","content":"---\n- name: Check if MySQL is already installed.\n ansible.builtin.stat:\n path: \"{{ mysql_config_file }}\"\n register: mysql_installed\n\n- name: Update apt cache if MySQL is not yet installed.\n ansible.builtin.apt:\n update_cache: true\n changed_when: false\n when: not mysql_installed.stat.exists\n\n- name: Ensure MySQL Python libraries are installed.\n ansible.builtin.apt:\n name: \"{{ mysql_python_package_debian }}\"\n state: present\n\n- name: Ensure MySQL packages are installed.\n ansible.builtin.apt:\n name: \"{{ mysql_packages }}\"\n state: present\n policy_rc_d: 101\n register: deb_mysql_install_packages\n\n# Because Ubuntu starts MySQL as part of the install process, we need to stop\n# mysql and remove the logfiles in case the user set a custom log file size.\n- name: Ensure MySQL is stopped after initial install.\n ansible.builtin.service:\n name: \"{{ mysql_daemon }}\"\n state: stopped\n when: not mysql_installed.stat.exists\n\n- name: Delete innodb log files created by apt package after initial install.\n ansible.builtin.file:\n path: \"{{ mysql_datadir }}/{{ item }}\"\n state: absent\n with_items:\n - ib_logfile0\n - ib_logfile1\n when: >\n not mysql_installed.stat.exists\n and ansible_distribution_major_version | int \u003c 12\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":1267,"content_sha256":"97e2bdd4b1c0e0ebe92bc7401ad738c279bdac8e53417b604c8eb0488566c50a"},{"filename":"test/roles/geerlingguy.mysql/tasks/setup-RedHat.yml","content":"---\n- name: Ensure MySQL packages are installed.\n ansible.builtin.yum:\n name: \"{{ mysql_packages }}\"\n state: present\n enablerepo: \"{{ mysql_enablerepo | default(omit, true) }}\"\n register: rh_mysql_install_packages\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":225,"content_sha256":"64b2baa496151934eac43b8cf87a733c3c3d8ac85d89f63e7187bbefded0fded"},{"filename":"test/roles/geerlingguy.mysql/tasks/users.yml","content":"---\n- name: Ensure MySQL users are present.\n mysql_user:\n name: \"{{ item.name }}\"\n host: \"{{ item.host | default('localhost') }}\"\n password: \"{{ item.password }}\"\n priv: \"{{ item.priv | default('*.*:USAGE') }}\"\n state: \"{{ item.state | default('present') }}\"\n append_privs: \"{{ item.append_privs | default(false) }}\"\n encrypted: \"{{ item.encrypted | default(false) }}\"\n column_case_sensitive: \"{{ item.case_sensitive | default(false) }}\"\n update_password: \"{{ item.update_password | default('always') }}\"\n with_items: \"{{ mysql_users }}\"\n no_log: \"{{ mysql_hide_passwords }}\"\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":606,"content_sha256":"712c802516429df5ce6606b692023ba4ece8001462b54f792ab186f9ef772e31"},{"filename":"test/roles/geerlingguy.mysql/tasks/variables.yml","content":"---\n# Variable configuration.\n- name: Include OS-specific variables.\n ansible.builtin.include_vars: \"{{ item }}\"\n with_first_found:\n - files:\n - \"vars/{{ ansible_os_family }}-{{ ansible_distribution_major_version }}.yml\"\n - \"vars/{{ ansible_os_family }}.yml\"\n skip: true\n\n- name: Define mysql_packages.\n ansible.builtin.set_fact:\n mysql_packages: \"{{ __mysql_packages | list }}\"\n when: mysql_packages is not defined\n\n- name: Define mysql_daemon.\n ansible.builtin.set_fact:\n mysql_daemon: \"{{ __mysql_daemon }}\"\n when: mysql_daemon is not defined\n\n- name: Define mysql_slow_query_log_file.\n ansible.builtin.set_fact:\n mysql_slow_query_log_file: \"{{ __mysql_slow_query_log_file }}\"\n when: mysql_slow_query_log_file is not defined\n\n- name: Define mysql_log_error.\n ansible.builtin.set_fact:\n mysql_log_error: \"{{ __mysql_log_error }}\"\n when: mysql_log_error is not defined\n\n- name: Define mysql_syslog_tag.\n ansible.builtin.set_fact:\n mysql_syslog_tag: \"{{ __mysql_syslog_tag }}\"\n when: mysql_syslog_tag is not defined\n\n- name: Define mysql_pid_file.\n ansible.builtin.set_fact:\n mysql_pid_file: \"{{ __mysql_pid_file }}\"\n when: mysql_pid_file is not defined\n\n- name: Define mysql_config_file.\n ansible.builtin.set_fact:\n mysql_config_file: \"{{ __mysql_config_file }}\"\n when: mysql_config_file is not defined\n\n- name: Define mysql_config_include_dir.\n ansible.builtin.set_fact:\n mysql_config_include_dir: \"{{ __mysql_config_include_dir }}\"\n when: mysql_config_include_dir is not defined\n\n- name: Define mysql_socket.\n ansible.builtin.set_fact:\n mysql_socket: \"{{ __mysql_socket }}\"\n when: mysql_socket is not defined\n\n- name: Define mysql_supports_innodb_large_prefix.\n ansible.builtin.set_fact:\n mysql_supports_innodb_large_prefix: \"{{ __mysql_supports_innodb_large_prefix }}\"\n when: mysql_supports_innodb_large_prefix is not defined\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":1904,"content_sha256":"bce3224a3b0d95f66c5d3e229f9dd6a4bff4ab9439c301b789b9f083d5819532"},{"filename":"test/roles/geerlingguy.mysql/vars/Archlinux.yml","content":"---\n__mysql_daemon: mariadb\n__mysql_packages:\n - mariadb\n__mysql_slow_query_log_file: /var/log/mysql/mysql-slow.log\n__mysql_log_error: /var/log/mysql.err\n__mysql_syslog_tag: mysql\n__mysql_pid_file: /run/mysqld/mysqld.pid\n__mysql_config_file: /etc/mysql/my.cnf\n__mysql_config_include_dir: /etc/mysql/conf.d\n__mysql_socket: /run/mysqld/mysqld.sock\n__mysql_supports_innodb_large_prefix: true\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":390,"content_sha256":"27dba112869bc98a0e5f19ab1f5f3955fe8099e14e67be4016613917dbca5ecf"},{"filename":"test/roles/geerlingguy.mysql/vars/Debian-10.yml","content":"---\n__mysql_daemon: mariadb\n__mysql_packages:\n - default-mysql-server\nmysql_log_file_group: adm\n__mysql_slow_query_log_file: /var/log/mysql/mysql-slow.log\n__mysql_log_error: /var/log/mysql/mysql.log\n__mysql_syslog_tag: mariadb\n__mysql_pid_file: /run/mysqld/mysqld.pid\n__mysql_config_file: /etc/mysql/my.cnf\n__mysql_config_include_dir: /etc/mysql/conf.d\n__mysql_socket: /run/mysqld/mysqld.sock\n__mysql_supports_innodb_large_prefix: true\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":437,"content_sha256":"ac62a78957c2bb7a48bf4c3f95e1d99fd8edc20b2ff3d4ca98bbb1077a80ae14"},{"filename":"test/roles/geerlingguy.mysql/vars/Debian-11.yml","content":"---\n__mysql_daemon: mariadb\n__mysql_packages:\n - default-mysql-server\nmysql_log_file_group: adm\n__mysql_slow_query_log_file: /var/log/mysql/mariadb-slow.log\n__mysql_log_error: /var/log/mysql/mysql.log\n__mysql_syslog_tag: mysqld\n__mysql_pid_file: /run/mysqld/mysqld.pid\n__mysql_config_file: /etc/mysql/my.cnf\n__mysql_config_include_dir: /etc/mysql/conf.d\n__mysql_socket: /run/mysqld/mysqld.sock\n__mysql_supports_innodb_large_prefix: true\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":438,"content_sha256":"eb63eaa5ad68a13746ef21a85e8134866b7fba070c6042b47333a17b48c4a8c9"},{"filename":"test/roles/geerlingguy.mysql/vars/Debian-12.yml","content":"---\n__mysql_daemon: mariadb\n__mysql_packages:\n - default-mysql-server\nmysql_log_file_group: adm\n__mysql_slow_query_log_file: /var/log/mysql/mariadb-slow.log\n__mysql_log_error: /var/log/mysql/mysql.log\n__mysql_syslog_tag: mysqld\n__mysql_pid_file: /run/mysqld/mysqld.pid\n__mysql_config_file: /etc/mysql/my.cnf\n__mysql_config_include_dir: /etc/mysql/conf.d\n__mysql_socket: /run/mysqld/mysqld.sock\n__mysql_supports_innodb_large_prefix: false\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":439,"content_sha256":"113aa6cdec12ed4704f3ab221888d45df6d5d50a761d2d6da1543e1f1a335da2"},{"filename":"test/roles/geerlingguy.mysql/vars/Debian-13.yml","content":"---\n__mysql_daemon: mariadb\n__mysql_packages:\n - default-mysql-server\nmysql_log_file_group: adm\n__mysql_slow_query_log_file: /var/log/mysql/mariadb-slow.log\n__mysql_log_error: /var/log/mysql/mysql.log\n__mysql_syslog_tag: mysqld\n__mysql_pid_file: /run/mysqld/mysqld.pid\n__mysql_config_file: /etc/mysql/my.cnf\n__mysql_config_include_dir: /etc/mysql/conf.d\n__mysql_socket: /run/mysqld/mysqld.sock\n__mysql_supports_innodb_large_prefix: false\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":439,"content_sha256":"113aa6cdec12ed4704f3ab221888d45df6d5d50a761d2d6da1543e1f1a335da2"},{"filename":"test/roles/geerlingguy.mysql/vars/Debian.yml","content":"---\n__mysql_daemon: mysql\n__mysql_packages:\n - mysql-common\n - mysql-server\nmysql_log_file_group: adm\n__mysql_slow_query_log_file: /var/log/mysql/mysql-slow.log\n__mysql_log_error: /var/log/mysql/mysql.err\n__mysql_syslog_tag: mysql\n__mysql_pid_file: /var/run/mysqld/mysqld.pid\n__mysql_config_file: /etc/mysql/my.cnf\n__mysql_config_include_dir: /etc/mysql/conf.d\n__mysql_socket: /var/run/mysqld/mysqld.sock\n__mysql_supports_innodb_large_prefix: true\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":450,"content_sha256":"e239fb087bdf3043ee5447162830d8045aa2af9dae3858d4ebd29ef04620d748"},{"filename":"test/roles/geerlingguy.mysql/vars/RedHat-10.yml","content":"---\n__mysql_daemon: mariadb\n__mysql_packages:\n - mariadb\n - mariadb-server\n - mariadb-connector-c\n - python3-PyMySQL\n - perl-DBD-MySQL\n__mysql_slow_query_log_file: /var/log/mysql-slow.log\n__mysql_log_error: /var/log/mariadb/mariadb.log\n__mysql_syslog_tag: mariadb\n__mysql_pid_file: /var/run/mariadb/mariadb.pid\n__mysql_config_file: /etc/my.cnf\n__mysql_config_include_dir: /etc/my.cnf.d\n__mysql_socket: /var/lib/mysql/mysql.sock\n# The entries controlled by this value should not be used with MariaDB >= 10.2.2\n# See https://github.com/frappe/bench/issues/681#issuecomment-398984706\n__mysql_supports_innodb_large_prefix: false\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":630,"content_sha256":"6f964630a7fb368e67c62ee75bfdc1fcd4df119cf69fda843fc505ba2aeedfaf"},{"filename":"test/roles/geerlingguy.mysql/vars/RedHat-7.yml","content":"---\n__mysql_daemon: mariadb\n__mysql_packages:\n - mariadb\n - mariadb-server\n - mariadb-libs\n - MySQL-python\n - perl-DBD-MySQL\n__mysql_slow_query_log_file: /var/log/mysql-slow.log\n__mysql_log_error: /var/log/mariadb/mariadb.log\n__mysql_syslog_tag: mariadb\n__mysql_pid_file: /var/run/mariadb/mariadb.pid\n__mysql_config_file: /etc/my.cnf\n__mysql_config_include_dir: /etc/my.cnf.d\n__mysql_socket: /var/lib/mysql/mysql.sock\n__mysql_supports_innodb_large_prefix: true\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":466,"content_sha256":"9598b3f58d15ea67d386f8d50928c9ba972f73c6c51309932dd61ff6a21ebd53"},{"filename":"test/roles/geerlingguy.mysql/vars/RedHat-8.yml","content":"---\n__mysql_daemon: mariadb\n__mysql_packages:\n - mariadb\n - mariadb-server\n - mariadb-connector-c\n - python3-PyMySQL\n - perl-DBD-MySQL\n__mysql_slow_query_log_file: /var/log/mysql-slow.log\n__mysql_log_error: /var/log/mariadb/mariadb.log\n__mysql_syslog_tag: mariadb\n__mysql_pid_file: /var/run/mariadb/mariadb.pid\n__mysql_config_file: /etc/my.cnf\n__mysql_config_include_dir: /etc/my.cnf.d\n__mysql_socket: /var/lib/mysql/mysql.sock\n# The entries controlled by this value should not be used with MariaDB >= 10.2.2\n# See https://github.com/frappe/bench/issues/681#issuecomment-398984706\n__mysql_supports_innodb_large_prefix: false\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":630,"content_sha256":"6f964630a7fb368e67c62ee75bfdc1fcd4df119cf69fda843fc505ba2aeedfaf"},{"filename":"test/roles/geerlingguy.mysql/vars/RedHat-9.yml","content":"---\n__mysql_daemon: mariadb\n__mysql_packages:\n - mariadb\n - mariadb-server\n - mariadb-connector-c\n - python3-PyMySQL\n - perl-DBD-MySQL\n__mysql_slow_query_log_file: /var/log/mysql-slow.log\n__mysql_log_error: /var/log/mariadb/mariadb.log\n__mysql_syslog_tag: mariadb\n__mysql_pid_file: /var/run/mariadb/mariadb.pid\n__mysql_config_file: /etc/my.cnf\n__mysql_config_include_dir: /etc/my.cnf.d\n__mysql_socket: /var/lib/mysql/mysql.sock\n# The entries controlled by this value should not be used with MariaDB >= 10.2.2\n# See https://github.com/frappe/bench/issues/681#issuecomment-398984706\n__mysql_supports_innodb_large_prefix: false\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":630,"content_sha256":"6f964630a7fb368e67c62ee75bfdc1fcd4df119cf69fda843fc505ba2aeedfaf"},{"filename":"test/roles/geerlingguy.mysql/vars/Ubuntu.yml","content":"---\n__mysql_log_error: /var/log/mysql/error.log\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":48,"content_sha256":"1344e9dd2798a0cd55337d6983d0881ab84066529138f5e1f56d9b10169aee02"},{"filename":"test/test_regressions.sh","content":"#!/usr/bin/env bash\nset -euo pipefail\n\nSKILL_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/..\" && pwd)\"\nSCRIPTS_DIR=\"$SKILL_DIR/scripts\"\nTEST_DIR=\"$SKILL_DIR/test\"\nREAL_PYTHON3=\"$(command -v python3)\"\nTMP_ROOT=\"$(mktemp -d -t ansible-validator-regressions.XXXXXX)\"\n\ncleanup() {\n if [ -n \"${TMP_ROOT:-}\" ] && [ -d \"$TMP_ROOT\" ]; then\n rm -rf \"$TMP_ROOT\"\n fi\n}\ntrap cleanup EXIT\n\nstrip_ansi() {\n \"$REAL_PYTHON3\" -c 'import re, sys; sys.stdout.write(re.sub(r\"\\x1b\\[[0-9;]*[A-Za-z]\", \"\", sys.stdin.read()))'\n}\n\nfail() {\n echo \"FAIL: $1\" >&2\n exit 1\n}\n\nassert_contains() {\n local text=\"$1\"\n local needle=\"$2\"\n local msg=\"$3\"\n if [[ \"$text\" != *\"$needle\"* ]]; then\n fail \"$msg\"\n fi\n}\n\nassert_not_contains() {\n local text=\"$1\"\n local needle=\"$2\"\n local msg=\"$3\"\n if [[ \"$text\" == *\"$needle\"* ]]; then\n fail \"$msg\"\n fi\n}\n\ntest_extract_mixed_import_playbook() {\n local fixture=\"$TEST_DIR/playbooks/regression-mixed-import.yml\"\n local output\n\n output=\"$(bash \"$SCRIPTS_DIR/extract_ansible_info_wrapper.sh\" \"$fixture\")\"\n\n if ! \"$REAL_PYTHON3\" -c '\nimport json\nimport sys\n\ndata = json.load(sys.stdin)\nmodules = set(data.get(\"modules\", []))\n\nmissing = sorted({\"shell\"} - modules)\nforbidden = sorted(modules.intersection({\"hosts\", \"tasks\", \"import_playbook\"}))\n\nif missing or forbidden:\n print(f\"missing={missing}; forbidden={forbidden}\")\n raise SystemExit(1)\n' \u003c\u003c\u003c \"$output\"\n then\n fail \"extract_ansible_info regression: mixed import playbook classification is wrong\"\n fi\n}\n\ntest_check_fqcn_inline_module() {\n local fixture=\"$TEST_DIR/playbooks/regression-inline-fqcn.yml\"\n local output\n\n output=\"$(bash \"$SCRIPTS_DIR/check_fqcn.sh\" \"$fixture\" | strip_ansi)\"\n\n assert_contains \"$output\" \"[NON-FQCN] shell\" \\\n \"check_fqcn regression: inline short module 'shell: \u003ccmd>' was not detected\"\n assert_not_contains \"$output\" \"[NON-FQCN] group\" \\\n \"check_fqcn regression: module argument key 'group:' was falsely detected as module usage\"\n}\n\ntest_inventory_yamllint_unavailable_not_pass() {\n local case_dir=\"$TMP_ROOT/case-yamllint-missing\"\n local bin_dir=\"$case_dir/bin\"\n local inventory_file=\"$case_dir/hosts.yml\"\n local output\n\n mkdir -p \"$bin_dir\"\n cat > \"$inventory_file\" \u003c\u003c'YAML'\nall:\n hosts:\n example:\n ansible_host: 127.0.0.1\nYAML\n\n cat > \"$bin_dir/python3\" \u003c\u003cEOF\n#!/usr/bin/env bash\nset -euo pipefail\nif [[ \"\\${1:-}\" == \"-m\" && \"\\${2:-}\" == \"venv\" ]]; then\n venv_dir=\"\\${3:?}\"\n mkdir -p \"\\$venv_dir/bin\"\n cat > \"\\$venv_dir/bin/activate\" \u003c\u003cACT\nexport PATH=\"\\$venv_dir/bin:\\\\\\$PATH\"\nACT\n ln -sf \"$REAL_PYTHON3\" \"\\$venv_dir/bin/python3\"\n cat > \"\\$venv_dir/bin/ansible-inventory\" \u003c\u003c'STUB'\n#!/usr/bin/env bash\nset -euo pipefail\nif [[ \"\\${1:-}\" == \"-i\" ]]; then\n shift 2\nfi\ncase \"\\${1:-}\" in\n --list)\n cat \u003c\u003c'JSON'\n{\"_meta\":{\"hostvars\":{\"example\":{\"ansible_host\":\"127.0.0.1\"}}},\"all\":{\"hosts\":[\"example\"]}}\nJSON\n ;;\n --graph)\n echo \"@all:\"\n echo \" |--example\"\n ;;\n *)\n exit 1\n ;;\nesac\nSTUB\n chmod +x \"\\$venv_dir/bin/ansible-inventory\"\n exit 0\nfi\nexec \"$REAL_PYTHON3\" \"\\$@\"\nEOF\n chmod +x \"$bin_dir/python3\"\n\n cat > \"$bin_dir/pip\" \u003c\u003c'EOF'\n#!/usr/bin/env bash\nexit 0\nEOF\n chmod +x \"$bin_dir/pip\"\n\n output=\"$(PATH=\"$bin_dir:/usr/bin:/bin\" bash \"$SCRIPTS_DIR/validate_inventory.sh\" \"$inventory_file\" | strip_ansi)\"\n\n assert_contains \"$output\" \"YAML syntax check SKIPPED (yamllint unavailable)\" \\\n \"validate_inventory regression: missing yamllint should mark stage as skipped\"\n assert_not_contains \"$output\" \"YAML syntax check passed\" \\\n \"validate_inventory regression: missing yamllint must not report YAML PASS\"\n}\n\ntest_inventory_localhost_json_check() {\n local case_dir=\"$TMP_ROOT/case-localhost-check\"\n local bin_dir=\"$case_dir/bin\"\n local inventory_file=\"$TEST_DIR/inventory/localhost-nested.yml\"\n local output\n\n mkdir -p \"$bin_dir\"\n\n cat > \"$bin_dir/ansible-inventory\" \u003c\u003c'EOF'\n#!/usr/bin/env bash\nset -euo pipefail\nif [[ \"${1:-}\" == \"--version\" ]]; then\n echo \"ansible-inventory [core 2.18.0]\"\n exit 0\nfi\nif [[ \"${1:-}\" == \"-i\" ]]; then\n shift 2\nfi\ncase \"${1:-}\" in\n --list)\n cat \u003c\u003c'JSON'\n{\"_meta\":{\"hostvars\":{\"localhost\":{}}},\"all\":{\"hosts\":[\"localhost\"]}}\nJSON\n ;;\n --graph)\n echo \"@all:\"\n echo \" |--localhost\"\n ;;\n *)\n exit 1\n ;;\nesac\nEOF\n chmod +x \"$bin_dir/ansible-inventory\"\n\n cat > \"$bin_dir/yamllint\" \u003c\u003c'EOF'\n#!/usr/bin/env bash\nexit 0\nEOF\n chmod +x \"$bin_dir/yamllint\"\n\n output=\"$(PATH=\"$bin_dir:/usr/bin:/bin\" bash \"$SCRIPTS_DIR/validate_inventory.sh\" \"$inventory_file\" | strip_ansi)\"\n\n assert_contains \"$output\" \"'localhost' defined without ansible_connection=local\" \\\n \"validate_inventory regression: localhost warning should come from parsed inventory JSON\"\n}\n\ntest_extract_mixed_import_playbook\ntest_check_fqcn_inline_module\ntest_inventory_yamllint_unavailable_not_pass\ntest_inventory_localhost_json_check\n\necho \"All ansible-validator regression tests passed.\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":5042,"content_sha256":"497a522a1b4ceec9319d717f021ff9262cd0ac06aa2cb6ffb8470c6ee2d95080"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Ansible Validator","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Overview","type":"text"}]},{"type":"paragraph","content":[{"text":"Comprehensive toolkit for validating, linting, and testing Ansible playbooks, roles, and collections. This skill provides automated workflows for ensuring Ansible code quality, syntax validation, dry-run testing with check mode and molecule, and intelligent documentation lookup for custom modules and collections with version awareness.","type":"text"}]},{"type":"paragraph","content":[{"text":"Default behavior:","type":"text","marks":[{"type":"strong"}]},{"text":" When validating any Ansible role with a ","type":"text"},{"text":"molecule/","type":"text","marks":[{"type":"code_inline"}]},{"text":" directory, attempt Molecule automatically using ","type":"text"},{"text":"bash scripts/test_role.sh \u003crole-path>","type":"text","marks":[{"type":"code_inline"}]},{"text":". If Molecule cannot run due to environment/runtime limits, mark Molecule as ","type":"text"},{"text":"BLOCKED","type":"text","marks":[{"type":"code_inline"}]},{"text":", report why, and continue all non-Molecule validation steps.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Trigger Guidance","type":"text"}]},{"type":"paragraph","content":[{"text":"Use this skill when the request is about validating or debugging existing Ansible code, not generating new code.","type":"text"}]},{"type":"paragraph","content":[{"text":"Common trigger phrases:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"validate this playbook\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"lint this role\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"why is ansible-lint failing\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"run check mode safely\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"test this role with molecule\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"find security issues in these Ansible files\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"module not found in this collection\"","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to Use This Skill","type":"text"}]},{"type":"paragraph","content":[{"text":"Apply this skill when encountering any of these scenarios:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Working with Ansible files (","type":"text"},{"text":".yml","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":".yaml","type":"text","marks":[{"type":"code_inline"}]},{"text":" playbooks, roles, inventories, vars)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Validating Ansible playbook syntax and structure","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Linting and formatting Ansible code","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Performing dry-run testing with ","type":"text"},{"text":"ansible-playbook --check","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Testing roles and playbooks with Molecule","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Debugging Ansible errors or misconfigurations","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Understanding custom Ansible modules, collections, or roles","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Ensuring infrastructure-as-code best practices","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Security validation of Ansible playbooks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Version compatibility checks for collections and modules","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Preflight (Run First)","type":"text"}]},{"type":"paragraph","content":[{"text":"Run preflight before validation to avoid dead ends:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"bash scripts/setup_tools.sh","type":"text"}]},{"type":"paragraph","content":[{"text":"Command path assumption: run commands from this skill root (","type":"text"},{"text":"devops-skills-plugin/skills/ansible-validator","type":"text","marks":[{"type":"code_inline"}]},{"text":") or use absolute paths.","type":"text"}]},{"type":"paragraph","content":[{"text":"Preflight requirements:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Baseline validation: ","type":"text"},{"text":"ansible","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ansible-playbook","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ansible-lint","type":"text","marks":[{"type":"code_inline"}]},{"text":" (plus ","type":"text"},{"text":"yamllint","type":"text","marks":[{"type":"code_inline"}]},{"text":" recommended)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Molecule execution: ","type":"text"},{"text":"molecule","type":"text","marks":[{"type":"code_inline"}]},{"text":" plus an available runtime (","type":"text"},{"text":"docker","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"podman","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Security scanning: ","type":"text"},{"text":"checkov","type":"text","marks":[{"type":"code_inline"}]},{"text":" (wrapper can bootstrap if missing)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Deterministic fallback rules:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If baseline tools are missing but Python + pip are available, wrapper scripts bootstrap temporary environments automatically.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If wrapper bootstrap fails (offline index, pip failure, missing Python), run direct commands for available tools, mark missing stages as ","type":"text"},{"text":"BLOCKED","type":"text","marks":[{"type":"code_inline"}]},{"text":", and continue.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If Molecule runtime is unavailable (Docker/Podman missing or daemon not running), skip Molecule execution, mark as ","type":"text"},{"text":"BLOCKED","type":"text","marks":[{"type":"code_inline"}]},{"text":", and continue remaining stages.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Wrapper vs Direct Command Routing","type":"text"}]},{"type":"paragraph","content":[{"text":"Use wrappers by default for consistent behavior and fallback handling.","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Validation scenario","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Default command","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use direct command when","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fallback if command cannot run","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Playbook syntax/lint","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"bash scripts/validate_playbook.sh \u003cplaybook.yml>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"User asks for a single focused check only (","type":"text"},{"text":"ansible-playbook --syntax-check","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ansible-lint","type":"text","marks":[{"type":"code_inline"}]},{"text":", or ","type":"text"},{"text":"yamllint","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Run any available direct checks and report skipped checks as ","type":"text"},{"text":"BLOCKED","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Role structural validation","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"bash scripts/validate_role.sh \u003crole-dir>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"User asks only for specific sub-checks (for example, structure only)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Run structure/YAML checks that are possible and report missing stages","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Role Molecule execution","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"bash scripts/test_role.sh \u003crole-dir> [scenario]","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"User explicitly asks for manual stage-by-stage Molecule commands","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Mark Molecule ","type":"text"},{"text":"BLOCKED","type":"text","marks":[{"type":"code_inline"}]},{"text":" with reason and continue non-Molecule role checks","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Security scanning","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"bash scripts/validate_playbook_security.sh \u003cpath>","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"bash scripts/validate_role_security.sh \u003cpath>","type":"text","marks":[{"type":"code_inline"}]},{"text":" plus ","type":"text"},{"text":"bash scripts/scan_secrets.sh \u003cpath>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"User requests raw Checkov output formatting or custom flags","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Run whichever scanner is available; if one is missing, run the other and report coverage gap","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Module/collection discovery","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"bash scripts/extract_ansible_info_wrapper.sh \u003cpath>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Python environment is already known-good and user wants direct parser output","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"If extraction fails, manually inspect ","type":"text"},{"text":"requirements.yml","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"galaxy.yml","type":"text","marks":[{"type":"code_inline"}]},{"text":" and continue with best-effort lookup","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Validation Workflow","type":"text"}]},{"type":"paragraph","content":[{"text":"Follow this deterministic workflow and never stop at a missing dependency:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"0. Preflight\n ├─> Run: bash scripts/setup_tools.sh\n ├─> Record tool/runtime readiness\n └─> Continue even when optional tools are missing\n\n1. Identify scope\n ├─> Single playbook validation\n ├─> Role validation\n ├─> Collection validation\n └─> Multi-playbook/inventory validation\n\n2. Syntax Validation\n ├─> Run ansible-playbook --syntax-check\n ├─> Run yamllint for YAML syntax\n └─> Report as PASS/FAIL/BLOCKED\n\n3. Lint and Best Practices\n ├─> Run ansible-lint (comprehensive linting)\n ├─> Check for deprecated modules (see references/module_alternatives.md)\n ├─> **DETECT NON-FQCN MODULE USAGE** (apt vs ansible.builtin.apt)\n │ └─> Run bash scripts/check_fqcn.sh to identify short module names\n │ └─> Recommend FQCN alternatives from references/module_alternatives.md\n ├─> Verify role structure\n └─> Report linting issues\n\n4. Dry-Run Testing (check mode)\n ├─> Run ansible-playbook --check (if inventory available)\n ├─> Analyze what would change\n └─> Report potential issues\n\n5. Molecule Testing (for roles with molecule/) - AUTOMATIC ATTEMPT\n ├─> Check if molecule/ directory exists in role\n ├─> If present, run: bash scripts/test_role.sh \u003crole-path> [scenario]\n ├─> If script exits 2, mark Molecule as BLOCKED (environment/runtime issue)\n ├─> If script exits 1, mark Molecule as FAIL (role/test issue)\n └─> Continue remaining validation regardless of Molecule outcome\n\n6. Custom Module/Collection Analysis (if detected)\n ├─> Extract module/collection information\n ├─> Identify versions\n ├─> Lookup documentation (Context7 first, then web.search_query fallback)\n └─> Provide version-specific guidance\n\n7. Security and Best Practices Review - DUAL SCANNING DEFAULT\n ├─> Run bash scripts/validate_playbook_security.sh or validate_role_security.sh (Checkov)\n ├─> Run bash scripts/scan_secrets.sh for hardcoded secret detection\n │ └─> This catches secrets Checkov may miss (passwords, API keys, tokens)\n ├─> If one scanner is unavailable, run the other and report reduced coverage\n ├─> Validate privilege escalation\n ├─> Review file permissions\n └─> Identify common anti-patterns\n\n8. Reference Routing\n ├─> Map each error/warning class to the matching reference file\n ├─> Extract concrete remediation from references (not file-name-only mention)\n └─> Include source section + fix guidance in final report\n\n9. Final Report (required format)\n ├─> Summary counts: PASS / FAIL / BLOCKED / SKIPPED\n ├─> Findings grouped by severity\n ├─> Tool/runtime blockers with exact command that failed\n └─> Next actions to reach full validation coverage","type":"text"}]},{"type":"paragraph","content":[{"text":"Status contract:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"BLOCKED","type":"text","marks":[{"type":"code_inline"}]},{"text":" means validation could not run due to environment/runtime constraints; ","type":"text"},{"text":"FAIL","type":"text","marks":[{"type":"code_inline"}]},{"text":" means the Ansible code or tests failed.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Error-Class Reference Routing","type":"text"}]},{"type":"paragraph","content":[{"text":"When issues are detected, consult the mapped reference and include a specific remediation excerpt in the report.","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Error class","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Typical detector","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Required reference","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Required action","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"YAML parse/format errors","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"yamllint","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ansible-playbook --syntax-check","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/common_errors.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" (Syntax Errors)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Quote the matching syntax fix pattern and apply corrected YAML structure","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Module/action resolution errors","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ansible-playbook","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ansible-lint","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/common_errors.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" (Module/Collection Errors)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Provide install/version fix commands (","type":"text"},{"text":"ansible-galaxy collection install ...","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Deprecated or non-FQCN module usage","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ansible-lint","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"bash scripts/check_fqcn.sh","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/module_alternatives.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Provide exact FQCN/module replacement per finding","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Template/variable errors","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ansible-playbook","type":"text","marks":[{"type":"code_inline"}]},{"text":", check mode","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/common_errors.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" (Template/Variable Errors), ","type":"text"},{"text":"references/best_practices.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" (Variable Management)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Recommend ","type":"text"},{"text":"default()","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"required()","type":"text","marks":[{"type":"code_inline"}]},{"text":", or type conversion fixes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Connection/inventory/privilege errors","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ansible-playbook --check","type":"text","marks":[{"type":"code_inline"}]},{"text":", runtime output","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/common_errors.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" (Connection, Inventory, Privilege sections)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Provide corrected inventory/auth/become configuration","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Security policy failures (CKV_*)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"validate_*_security.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" / Checkov","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/security_checklist.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Map failed policy to a secure task rewrite","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Hardcoded secrets","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"bash scripts/scan_secrets.sh","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/security_checklist.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" (Secrets Management)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Replace with Vault/env/external secret manager approach","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Role structure/idempotency warnings","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"validate_role.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":", Molecule idempotence","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/best_practices.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Provide role layout or idempotency remediation steps","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"External documentation lookup trigger:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If the issue involves a custom/private collection or unknown module parameters not covered locally, run module discovery + documentation lookup (see section 7).","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Core Capabilities","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"1. YAML Syntax Validation","type":"text"}]},{"type":"paragraph","content":[{"text":"Purpose:","type":"text","marks":[{"type":"strong"}]},{"text":" Ensure YAML files are syntactically correct before Ansible parsing.","type":"text"}]},{"type":"paragraph","content":[{"text":"Tools:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"yamllint","type":"text","marks":[{"type":"code_inline"}]},{"text":" - YAML linter for syntax and formatting","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ansible-playbook --syntax-check","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Ansible-specific syntax validation","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Workflow:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Check YAML syntax with yamllint\nyamllint playbook.yml\n\n# Or for entire directory\nyamllint -c .yamllint .\n\n# Check Ansible playbook syntax\nansible-playbook playbook.yml --syntax-check","type":"text"}]},{"type":"paragraph","content":[{"text":"Common Issues Detected:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Indentation errors","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Invalid YAML syntax","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Duplicate keys","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Trailing whitespace","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Line length violations","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Missing colons or quotes","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Best Practices:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Always run yamllint before ansible-lint","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use 2-space indentation consistently","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Configure yamllint rules in ","type":"text"},{"text":".yamllint","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Fix YAML syntax errors first, then Ansible-specific issues","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"2. Ansible Lint","type":"text"}]},{"type":"paragraph","content":[{"text":"Purpose:","type":"text","marks":[{"type":"strong"}]},{"text":" Enforce Ansible best practices and catch common errors.","type":"text"}]},{"type":"paragraph","content":[{"text":"Workflow:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Lint a single playbook\nansible-lint playbook.yml\n\n# Lint all playbooks in directory\nansible-lint .\n\n# Lint with specific rules\nansible-lint -t yaml,syntax playbook.yml\n\n# Skip specific rules\nansible-lint -x yaml[line-length] playbook.yml\n\n# Output parseable format\nansible-lint -f pep8 playbook.yml\n\n# Show rule details\nansible-lint -L","type":"text"}]},{"type":"paragraph","content":[{"text":"Common Issues Detected:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Deprecated modules or syntax","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Missing task names","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Improper use of ","type":"text"},{"text":"command","type":"text","marks":[{"type":"code_inline"}]},{"text":" vs ","type":"text"},{"text":"shell","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Unquoted template expressions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Hard-coded values that should be variables","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Missing ","type":"text"},{"text":"become","type":"text","marks":[{"type":"code_inline"}]},{"text":" directives","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Inefficient task patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Jinja2 template errors","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Incorrect variable usage","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Role dependencies issues","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Severity Levels:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Error:","type":"text","marks":[{"type":"strong"}]},{"text":" Must fix - will cause failures","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Warning:","type":"text","marks":[{"type":"strong"}]},{"text":" Should fix - potential issues","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Info:","type":"text","marks":[{"type":"strong"}]},{"text":" Consider fixing - best practice violations","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Auto-fix approach:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ansible-lint supports ","type":"text"},{"text":"--fix","type":"text","marks":[{"type":"code_inline"}]},{"text":" for auto-fixable issues","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Always review changes before applying","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Some issues require manual intervention","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"3. Security Scanning (Checkov)","type":"text"}]},{"type":"paragraph","content":[{"text":"Purpose:","type":"text","marks":[{"type":"strong"}]},{"text":" Identify security vulnerabilities and compliance violations in Ansible code using Checkov, a static code analysis tool for infrastructure-as-code.","type":"text"}]},{"type":"paragraph","content":[{"text":"What Checkov Provides Beyond ansible-lint:","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"While ansible-lint focuses on code quality and best practices, Checkov specifically targets security policies and compliance:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"SSL/TLS Security:","type":"text","marks":[{"type":"strong"}]},{"text":" Certificate validation enforcement","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"HTTPS Enforcement:","type":"text","marks":[{"type":"strong"}]},{"text":" Ensures secure protocols for downloads","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Package Security:","type":"text","marks":[{"type":"strong"}]},{"text":" GPG signature verification for packages","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Cloud Security:","type":"text","marks":[{"type":"strong"}]},{"text":" AWS, Azure, GCP misconfiguration detection","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Compliance Frameworks:","type":"text","marks":[{"type":"strong"}]},{"text":" Maps to security standards","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Network Security:","type":"text","marks":[{"type":"strong"}]},{"text":" Firewall and network policy validation","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Workflow:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Scan playbook for security issues\nbash scripts/validate_playbook_security.sh playbook.yml\n\n# Scan entire directory\nbash scripts/validate_playbook_security.sh /path/to/playbooks/\n\n# Scan role for security issues\nbash scripts/validate_role_security.sh roles/webserver/\n\n# Direct checkov usage\ncheckov -d . --framework ansible\n\n# Scan with specific output format\ncheckov -d . --framework ansible --output json\n\n# Scan and skip specific checks\ncheckov -d . --framework ansible --skip-check CKV_ANSIBLE_1","type":"text"}]},{"type":"paragraph","content":[{"text":"Common Security Issues Detected:","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"Certificate Validation:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CKV_ANSIBLE_1:","type":"text","marks":[{"type":"strong"}]},{"text":" URI module disabling certificate validation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CKV_ANSIBLE_2:","type":"text","marks":[{"type":"strong"}]},{"text":" get_url disabling certificate validation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CKV_ANSIBLE_3:","type":"text","marks":[{"type":"strong"}]},{"text":" yum disabling certificate validation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CKV_ANSIBLE_4:","type":"text","marks":[{"type":"strong"}]},{"text":" yum disabling SSL verification","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"HTTPS Enforcement:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CKV2_ANSIBLE_1:","type":"text","marks":[{"type":"strong"}]},{"text":" URI module using HTTP instead of HTTPS","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CKV2_ANSIBLE_2:","type":"text","marks":[{"type":"strong"}]},{"text":" get_url using HTTP instead of HTTPS","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Package Security:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CKV_ANSIBLE_5:","type":"text","marks":[{"type":"strong"}]},{"text":" apt installing packages without GPG signature","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CKV_ANSIBLE_6:","type":"text","marks":[{"type":"strong"}]},{"text":" apt using force parameter bypassing signatures","type":"text"}]}]},{"type":"list_item","content":[{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CKV2_ANSIBLE_4:","type":"text","marks":[{"type":"em"}]},{"text":"* dnf installing packages without GPG signature","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CKV2_ANSIBLE_5:","type":"text","marks":[{"type":"strong"}]},{"text":" dnf disabling SSL verification","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CKV2_ANSIBLE_6:","type":"text","marks":[{"type":"strong"}]},{"text":" dnf disabling certificate validation","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Error Handling:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CKV2_ANSIBLE_3:","type":"text","marks":[{"type":"strong"}]},{"text":" Block missing error handling","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Cloud Security (when managing cloud resources):","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CKV_AWS_88:","type":"text","marks":[{"type":"strong"}]},{"text":" EC2 instances with public IPs","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CKV_AWS_135:","type":"text","marks":[{"type":"strong"}]},{"text":" EC2 instances without EBS optimization","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Example Violation:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"# BAD - Disables certificate validation\n- name: Download file\n get_url:\n url: https://example.com/file.tar.gz\n dest: /tmp/file.tar.gz\n validate_certs: false # Security issue!\n\n# GOOD - Certificate validation enabled\n- name: Download file\n get_url:\n url: https://example.com/file.tar.gz\n dest: /tmp/file.tar.gz\n validate_certs: true # Or omit (true by default)","type":"text"}]},{"type":"paragraph","content":[{"text":"Integration with Validation Workflow:","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"Checkov complements ansible-lint:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ansible-lint","type":"text","marks":[{"type":"strong"}]},{"text":" catches code quality issues, deprecated modules, best practices","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Checkov","type":"text","marks":[{"type":"strong"}]},{"text":" catches security vulnerabilities, compliance violations, cryptographic issues","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Best Practice:","type":"text","marks":[{"type":"strong"}]},{"text":" Run both tools for comprehensive validation:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Complete validation workflow\nbash scripts/validate_playbook.sh playbook.yml # Syntax + Lint\nbash scripts/validate_playbook_security.sh playbook.yml # Security","type":"text"}]},{"type":"paragraph","content":[{"text":"Output Format:","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"Checkov provides clear security scan results:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Security Scan Results:\n Passed: 15 checks\n Failed: 2 checks\n Skipped: 0 checks\n\nFailed Checks:\n Check: CKV_ANSIBLE_2 - \"Ensure that certificate validation isn't disabled with get_url\"\n FAILED for resource: tasks/main.yml:download_file\n File: /roles/webserver/tasks/main.yml:10-15","type":"text"}]},{"type":"paragraph","content":[{"text":"Remediation Resources:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Checkov Policy Index: https://www.checkov.io/5.Policy%20Index/ansible.html","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Ansible Security Checklist: ","type":"text"},{"text":"references/security_checklist.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Ansible Best Practices: ","type":"text"},{"text":"references/best_practices.md","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"paragraph","content":[{"text":"Installation:","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"Checkov is automatically installed in a temporary environment if not available system-wide. For permanent installation:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"pip3 install checkov","type":"text"}]},{"type":"paragraph","content":[{"text":"When to Use:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Before deploying to production","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"In CI/CD pipelines for automated security checks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"When working with sensitive infrastructure","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For compliance audits and security reviews","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"When downloading files or installing packages","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"When managing cloud resources with Ansible","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"4. Playbook Syntax Check","type":"text"}]},{"type":"paragraph","content":[{"text":"Purpose:","type":"text","marks":[{"type":"strong"}]},{"text":" Validate playbook syntax without executing tasks.","type":"text"}]},{"type":"paragraph","content":[{"text":"Workflow:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Basic syntax check\nansible-playbook playbook.yml --syntax-check\n\n# Syntax check with inventory\nansible-playbook -i inventory playbook.yml --syntax-check\n\n# Syntax check with extra vars\nansible-playbook playbook.yml --syntax-check -e @vars.yml\n\n# Check all playbooks\nfor file in *.yml; do\n ansible-playbook \"$file\" --syntax-check\ndone","type":"text"}]},{"type":"paragraph","content":[{"text":"Validation Checks:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"YAML parsing","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Playbook structure","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Task definitions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Variable references","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Module parameter syntax","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Jinja2 template syntax","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Include/import statements","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Error Handling:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Parse error messages for specific issues","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check for typos in module names","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Verify variable definitions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Ensure proper indentation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check file paths for includes/imports","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"5. Dry-Run Testing (Check Mode)","type":"text"}]},{"type":"paragraph","content":[{"text":"Purpose:","type":"text","marks":[{"type":"strong"}]},{"text":" Preview changes that would be made without actually applying them.","type":"text"}]},{"type":"paragraph","content":[{"text":"Workflow:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Run in check mode (dry-run)\nansible-playbook -i inventory playbook.yml --check\n\n# Check mode with diff\nansible-playbook -i inventory playbook.yml --check --diff\n\n# Check mode with verbose output\nansible-playbook -i inventory playbook.yml --check -v\n\n# Check mode for specific hosts\nansible-playbook -i inventory playbook.yml --check --limit webservers\n\n# Check mode with tags\nansible-playbook -i inventory playbook.yml --check --tags deploy\n\n# Step through tasks\nansible-playbook -i inventory playbook.yml --check --step","type":"text"}]},{"type":"paragraph","content":[{"text":"Check Mode Analysis:","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"When reviewing check mode output, focus on:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Task Changes:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ok","type":"text","marks":[{"type":"code_inline"}]},{"text":": No changes needed","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"changed","type":"text","marks":[{"type":"code_inline"}]},{"text":": Would make changes","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"failed","type":"text","marks":[{"type":"code_inline"}]},{"text":": Would fail (check for check_mode support)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"skipped","type":"text","marks":[{"type":"code_inline"}]},{"text":": Conditional skip","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Diff Output:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Shows exact changes to files","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Helps identify unintended modifications","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Useful for reviewing template changes","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Handlers:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Which handlers would be notified","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Service restarts that would occur","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Potential downtime","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Failed Tasks:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Some modules don't support check mode","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"May need ","type":"text"},{"text":"check_mode: no","type":"text","marks":[{"type":"code_inline"}]},{"text":" override","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Identify tasks that would fail","type":"text"}]}]}]}]}]},{"type":"paragraph","content":[{"text":"Limitations:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Not all modules support check mode","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Some tasks depend on previous changes","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"May not accurately reflect all changes","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Stateful operations may show unexpected results","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Safety Considerations:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Always run check mode before real execution","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Review diff output carefully","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test in non-production first","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Validate changes make sense","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check for unintended side effects","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"6. Molecule Testing","type":"text"}]},{"type":"paragraph","content":[{"text":"Purpose:","type":"text","marks":[{"type":"strong"}]},{"text":" Test Ansible roles in isolated environments with multiple scenarios.","type":"text"}]},{"type":"paragraph","content":[{"text":"Automatic attempt policy:","type":"text","marks":[{"type":"strong"}]},{"text":" When validating any Ansible role with a ","type":"text"},{"text":"molecule/","type":"text","marks":[{"type":"code_inline"}]},{"text":" directory, automatically attempt Molecule tests using ","type":"text"},{"text":"bash scripts/test_role.sh \u003crole-path> [scenario]","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"When to Use:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Automatically triggered when validating roles with molecule/ directory","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Testing roles before deployment","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Validating role compatibility across different OS versions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Integration testing for complex roles","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CI/CD pipeline validation","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Workflow:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Initialize molecule for a role\ncd roles/myrole\nmolecule init scenario --driver-name docker\n\n# List scenarios\nmolecule list\n\n# Run full test sequence\nmolecule test\n\n# Individual test stages\nmolecule create # Create test instances\nmolecule converge # Run Ansible against instances\nmolecule verify # Run verification tests\nmolecule destroy # Destroy test instances\n\n# Test with specific scenario\nmolecule test -s alternative\n\n# Debug mode\nmolecule --debug test\n\n# Keep instances for debugging\nmolecule converge\nmolecule login # SSH into test instance","type":"text"}]},{"type":"paragraph","content":[{"text":"Test Sequence:","type":"text","marks":[{"type":"strong"}]}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"dependency","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Install role dependencies","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"lint","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Run yamllint and ansible-lint","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"cleanup","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Clean up before testing","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"destroy","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Destroy existing instances","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"syntax","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Run syntax check","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"create","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Create test instances","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"prepare","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Prepare instances (install requirements)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"converge","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Run the role","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"idempotence","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Run again, verify no changes","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"side_effect","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Optional side effect playbook","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"verify","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Run verification tests (Testinfra, etc.)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"cleanup","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Final cleanup","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"destroy","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Destroy test instances","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Molecule Configuration:","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"Check ","type":"text"},{"text":"molecule/default/molecule.yml","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"dependency:\n name: galaxy\ndriver:\n name: docker\nplatforms:\n - name: instance\n image: ubuntu:22.04\nprovisioner:\n name: ansible\nverifier:\n name: ansible","type":"text"}]},{"type":"paragraph","content":[{"text":"Verification Tests:","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"Molecule supports multiple verifiers:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Ansible","type":"text","marks":[{"type":"strong"}]},{"text":" (built-in): Use Ansible tasks to verify","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Testinfra","type":"text","marks":[{"type":"strong"}]},{"text":": Python-based infrastructure tests","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Goss","type":"text","marks":[{"type":"strong"}]},{"text":": YAML-based server validation","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Example Ansible verifier (","type":"text"},{"text":"molecule/default/verify.yml","type":"text","marks":[{"type":"code_inline"}]},{"text":"):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"---\n- name: Verify\n hosts: all\n tasks:\n - name: Check service is running\n service:\n name: nginx\n state: started\n check_mode: true\n register: result\n failed_when: result.changed","type":"text"}]},{"type":"paragraph","content":[{"text":"Common Molecule Errors:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Driver not installed (docker, podman, vagrant)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Missing Python dependencies","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Platform image not available","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Network connectivity issues","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Insufficient permissions for driver","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Molecule Skip/Fallback Policy (Required):","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If ","type":"text"},{"text":"molecule/","type":"text","marks":[{"type":"code_inline"}]},{"text":" does not exist: mark Molecule as ","type":"text"},{"text":"SKIPPED","type":"text","marks":[{"type":"code_inline"}]},{"text":" and continue.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If ","type":"text"},{"text":"test_role.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" exits ","type":"text"},{"text":"2","type":"text","marks":[{"type":"code_inline"}]},{"text":": mark Molecule as ","type":"text"},{"text":"BLOCKED","type":"text","marks":[{"type":"code_inline"}]},{"text":" (missing/unavailable runtime dependency) and continue.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If ","type":"text"},{"text":"test_role.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" exits ","type":"text"},{"text":"1","type":"text","marks":[{"type":"code_inline"}]},{"text":": mark Molecule as ","type":"text"},{"text":"FAIL","type":"text","marks":[{"type":"code_inline"}]},{"text":" (role/test issue) and continue.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Never stop the full validation report because Molecule is blocked.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Use this reporting language for blocked Molecule runs:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"text"},"content":[{"text":"Molecule Status: BLOCKED\nReason: \u003cmissing dependency/runtime and failing command>\nFallback Applied: Completed syntax, lint, check-mode, and security validation without Molecule runtime tests.\nNext Action: \u003cinstall/start dependency>; rerun `bash scripts/test_role.sh \u003crole-path> [scenario]`","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"7. Custom Module and Collection Documentation Lookup","type":"text"}]},{"type":"paragraph","content":[{"text":"Purpose:","type":"text","marks":[{"type":"strong"}]},{"text":" Automatically discover and retrieve version-specific documentation for custom modules and collections using web search and Context7 MCP.","type":"text"}]},{"type":"paragraph","content":[{"text":"When to Trigger:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Encountering unfamiliar module usage","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Working with custom/private collections","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Debugging module-specific errors","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Understanding new module parameters","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Checking version compatibility","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Deprecated module alternatives","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Detection Workflow:","type":"text","marks":[{"type":"strong"}]}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extract Module Information:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"scripts/extract_ansible_info_wrapper.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" to parse playbooks and roles","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Identify module usage and collections","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extract version constraints from ","type":"text"},{"text":"requirements.yml","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extract Collection Information:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Identify collection namespaces (e.g., ","type":"text"},{"text":"community.general","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ansible.posix","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Determine collection versions from ","type":"text"},{"text":"requirements.yml","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"galaxy.yml","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Detect custom/private vs. public collections","type":"text"}]}]}]}]}]},{"type":"paragraph","content":[{"text":"Documentation Lookup Strategy:","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"Use this deterministic lookup order:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For public collections/modules:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Resolve library: ","type":"text"},{"text":"mcp__context7__resolve-library-id","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Query docs: ","type":"text"},{"text":"mcp__context7__query-docs","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If Context7 has no suitable result:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use web search via ","type":"text"},{"text":"web.search_query","type":"text","marks":[{"type":"code_inline"}]},{"text":" with versioned queries","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Prioritize official docs (docs.ansible.com, galaxy.ansible.com, vendor docs)","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For custom/private modules:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Prefer in-repo docs (","type":"text"},{"text":"README","type":"text","marks":[{"type":"code_inline"}]},{"text":", module docs, role docs) first","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Then use targeted web search with collection/module/version terms","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Always report source + version context used in final guidance","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Search Query Templates:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"# For custom modules\n\"[module-name] ansible module version [version] documentation\"\n\"[module-name] ansible [module-type] example\"\n\"ansible [collection-name].[module-name] parameters\"\n\n# For custom collections\n\"ansible collection [collection-name] version [version]\"\n\"[collection-namespace].[collection-name] ansible documentation\"\n\"ansible galaxy [collection-name] modules\"\n\n# For specific errors\n\"ansible [module-name] error: [error-message]\"\n\"ansible [collection-name] module failed\"","type":"text"}]},{"type":"paragraph","content":[{"text":"Example Workflow:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"User working with: community.docker.docker_container version 3.0.0\n\n1. Extract module info from playbook:\n tasks:\n - name: Start container\n community.docker.docker_container:\n name: myapp\n image: nginx:latest\n\n2. Detect collection: community.docker\n\n3. Search for documentation:\n - Try Context7: mcp__context7__resolve-library-id(\"ansible community.docker\")\n - Fallback to web.search_query(\"ansible community.docker collection version 3.0 docker_container module documentation\")\n\n4. If official docs found:\n - Parse module parameters (required vs optional)\n - Identify return values\n - Find usage examples\n - Check version compatibility\n\n5. Provide version-specific guidance to user","type":"text"}]},{"type":"paragraph","content":[{"text":"Version Compatibility Checks:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Compare required collection versions with available versions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Identify deprecated modules or parameters","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Suggest upgrade paths if using outdated versions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Warn about breaking changes between versions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check Ansible core version compatibility","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Common Collection Sources:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Ansible Galaxy","type":"text","marks":[{"type":"strong"}]},{"text":": Official community collections","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Red Hat Automation Hub","type":"text","marks":[{"type":"strong"}]},{"text":": Certified collections","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"GitHub","type":"text","marks":[{"type":"strong"}]},{"text":": Custom/private collections","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Internal repositories","type":"text","marks":[{"type":"strong"}]},{"text":": Company-specific collections","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"8. Security and Best Practices Validation","type":"text"}]},{"type":"paragraph","content":[{"text":"Purpose:","type":"text","marks":[{"type":"strong"}]},{"text":" Identify security vulnerabilities and anti-patterns in Ansible playbooks.","type":"text"}]},{"type":"paragraph","content":[{"text":"Security Checks:","type":"text","marks":[{"type":"strong"}]}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Secrets Detection:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Check for hardcoded credentials\ngrep -r \"password:\" *.yml\ngrep -r \"secret:\" *.yml\ngrep -r \"api_key:\" *.yml\ngrep -r \"token:\" *.yml","type":"text"}]},{"type":"paragraph","content":[{"text":"Remediation:","type":"text","marks":[{"type":"strong"}]},{"text":" Use Ansible Vault, environment variables, or external secret management","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Privilege Escalation:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Unnecessary use of ","type":"text"},{"text":"become: yes","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Missing ","type":"text"},{"text":"become_user","type":"text","marks":[{"type":"code_inline"}]},{"text":" specification","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Over-permissive sudo rules","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Running entire playbooks as root","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"File Permissions:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"World-readable sensitive files","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Missing mode parameter on file/template tasks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Incorrect ownership settings","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Sensitive files not encrypted with vault","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Command Injection:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Unvalidated variables in shell/command modules","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Missing ","type":"text"},{"text":"quote","type":"text","marks":[{"type":"code_inline"}]},{"text":" filter for user input","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Direct use of {{ var }} in command strings","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Network Security:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Unencrypted protocols (HTTP instead of HTTPS)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Missing SSL/TLS validation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Exposing services on 0.0.0.0","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Insecure default ports","type":"text"}]}]}]}]}]},{"type":"paragraph","content":[{"text":"Best Practices:","type":"text","marks":[{"type":"strong"}]}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Playbook Organization:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Logical task separation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Reusable roles for common patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Clear directory structure","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Meaningful playbook names","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Variable Management:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Vault encryption for sensitive data","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Clear variable naming conventions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Variable precedence awareness","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Group/host vars organization","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Default values using ","type":"text"},{"text":"default()","type":"text","marks":[{"type":"code_inline"}]},{"text":" filter","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Task Naming:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Descriptive task names","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Consistent naming convention","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Action-oriented descriptions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Include changed resource in name","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Idempotency:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"All tasks should be idempotent","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use proper modules instead of command/shell","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check mode compatibility","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Proper use of ","type":"text"},{"text":"creates","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"removes","type":"text","marks":[{"type":"code_inline"}]},{"text":" for command tasks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Avoid ","type":"text"},{"text":"changed_when: false","type":"text","marks":[{"type":"code_inline"}]},{"text":" unless necessary","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Error Handling:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"failed_when","type":"text","marks":[{"type":"code_inline"}]},{"text":" for custom failure conditions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Implement ","type":"text"},{"text":"block/rescue/always","type":"text","marks":[{"type":"code_inline"}]},{"text":" for error recovery","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Set appropriate ","type":"text"},{"text":"any_errors_fatal","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"ignore_errors","type":"text","marks":[{"type":"code_inline"}]},{"text":" sparingly","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Documentation:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"README for each role","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Variable documentation in defaults/main.yml","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Role metadata in meta/main.yml","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Playbook header comments","type":"text"}]}]}]}]}]},{"type":"paragraph","content":[{"text":"Reference Documentation:","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"For detailed security guidelines and best practices, refer to:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/security_checklist.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Common security vulnerabilities","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/best_practices.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Ansible coding standards","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/common_errors.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Common errors and solutions","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Tool Prerequisites","type":"text"}]},{"type":"paragraph","content":[{"text":"Run this preflight before validation:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Preferred one-shot preflight\nbash scripts/setup_tools.sh\n\n# Check Ansible installation\nansible --version\nansible-playbook --version\n\n# Check ansible-lint installation\nansible-lint --version\n\n# Check yamllint installation\nyamllint --version\n\n# Check molecule installation (for role testing with molecule/)\nmolecule --version\n\n# Check container runtime for Molecule\ndocker --version\ndocker info\n# or\npodman --version\npodman info\n\n# Install missing tools (example for pip)\npip install ansible ansible-lint yamllint ansible-compat\n\n# Install molecule with docker driver\npip install molecule molecule-docker\n\n# Install molecule with podman driver (alternative)\npip install molecule molecule-podman","type":"text"}]},{"type":"paragraph","content":[{"text":"Minimum Versions:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Ansible: >= 2.9 (recommend >= 2.12)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ansible-lint: >= 6.0.0","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"yamllint: >= 1.26.0","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"molecule: >= 3.4.0 (if testing roles)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Execution policy when tools are missing:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If ","type":"text"},{"text":"ansible","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"ansible-lint","type":"text","marks":[{"type":"code_inline"}]},{"text":" are missing, wrappers (","type":"text"},{"text":"validate_playbook.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"validate_role.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":") attempt temporary venv bootstrap.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If Molecule runtime (","type":"text"},{"text":"docker info","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"podman info","type":"text","marks":[{"type":"code_inline"}]},{"text":") is unavailable, Molecule is ","type":"text"},{"text":"BLOCKED","type":"text","marks":[{"type":"code_inline"}]},{"text":" and non-Molecule checks continue.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If ","type":"text"},{"text":"checkov","type":"text","marks":[{"type":"code_inline"}]},{"text":" is missing, security wrappers bootstrap it when possible; otherwise run ","type":"text"},{"text":"scan_secrets.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" and report reduced security coverage.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Optional Tools:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ansible-inventory","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Inventory validation and graphing","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ansible-doc","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Module documentation lookup","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"jq","type":"text","marks":[{"type":"code_inline"}]},{"text":" - JSON parsing for structured output","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Error Troubleshooting","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Common Errors and Solutions","type":"text"}]},{"type":"paragraph","content":[{"text":"Error: Module Not Found","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Solution: Install required collection with ansible-galaxy\nCheck collections/requirements.yml\nVerify collection namespace and name","type":"text"}]},{"type":"paragraph","content":[{"text":"Error: Undefined Variable","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Solution: Define variable in vars, defaults, or group_vars\nCheck variable precedence\nUse default() filter for optional variables\nVerify variable file is included","type":"text"}]},{"type":"paragraph","content":[{"text":"Error: Template Syntax Error","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Solution: Check Jinja2 template syntax\nVerify variable types match filters\nEnsure proper quote escaping\nTest template rendering separately","type":"text"}]},{"type":"paragraph","content":[{"text":"Error: Connection Failed","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Solution: Verify inventory host accessibility\nCheck SSH configuration and keys\nVerify ansible_host and ansible_port\nTest with ansible -m ping","type":"text"}]},{"type":"paragraph","content":[{"text":"Error: Permission Denied","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Solution: Add become: yes for privilege escalation\nVerify sudo/su configuration\nCheck file permissions\nVerify user has necessary privileges","type":"text"}]},{"type":"paragraph","content":[{"text":"Error: Deprecated Module","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Solution: Check ansible-lint output for replacement\nConsult module documentation for alternatives\nUpdate to recommended module\nTest functionality with new module","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Resources","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"scripts/","type":"text"}]},{"type":"paragraph","content":[{"text":"setup_tools.sh","type":"text","marks":[{"type":"strong"}]},{"text":" - Preflight checker for Ansible validator dependencies. Verifies baseline tools (","type":"text"},{"text":"ansible","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ansible-playbook","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ansible-lint","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"yamllint","type":"text","marks":[{"type":"code_inline"}]},{"text":") and Molecule runtime readiness (","type":"text"},{"text":"docker","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"podman","type":"text","marks":[{"type":"code_inline"}]},{"text":") and provides installation guidance.","type":"text"}]},{"type":"paragraph","content":[{"text":"Usage:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"bash scripts/setup_tools.sh","type":"text"}]},{"type":"paragraph","content":[{"text":"extract_ansible_info_wrapper.sh","type":"text","marks":[{"type":"strong"}]},{"text":" - Bash wrapper for extract_ansible_info.py that automatically handles PyYAML dependencies. Creates a temporary venv if PyYAML is not available in system Python.","type":"text"}]},{"type":"paragraph","content":[{"text":"Usage:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"bash scripts/extract_ansible_info_wrapper.sh \u003cpath-to-playbook-or-role>","type":"text"}]},{"type":"paragraph","content":[{"text":"Output: JSON structure with modules, collections, and versions","type":"text"}]},{"type":"paragraph","content":[{"text":"extract_ansible_info.py","type":"text","marks":[{"type":"strong"}]},{"text":" - Python script (called by wrapper) to parse Ansible playbooks and roles to extract module usage, collection dependencies, and version information. The wrapper script handles dependency management automatically.","type":"text"}]},{"type":"paragraph","content":[{"text":"validate_playbook.sh","type":"text","marks":[{"type":"strong"}]},{"text":" - Comprehensive validation script that runs syntax check, yamllint, and ansible-lint on playbooks. Automatically installs ansible and ansible-lint in a temporary venv if not available on the system (prefers system versions when available).","type":"text"}]},{"type":"paragraph","content":[{"text":"Usage:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"bash scripts/validate_playbook.sh \u003cplaybook.yml>","type":"text"}]},{"type":"paragraph","content":[{"text":"validate_playbook_security.sh","type":"text","marks":[{"type":"strong"}]},{"text":" - Security validation script that scans playbooks for security vulnerabilities using Checkov. Automatically installs checkov in a temporary venv if not available. Complements validate_playbook.sh by focusing on security-specific checks like SSL/TLS validation, HTTPS enforcement, and package signature verification.","type":"text"}]},{"type":"paragraph","content":[{"text":"Usage:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"bash scripts/validate_playbook_security.sh \u003cplaybook.yml>\n# Or scan entire directory\nbash scripts/validate_playbook_security.sh /path/to/playbooks/","type":"text"}]},{"type":"paragraph","content":[{"text":"validate_role.sh","type":"text","marks":[{"type":"strong"}]},{"text":" - Comprehensive role validation script that checks role structure, YAML syntax, Ansible syntax, linting, and molecule configuration.","type":"text"}]},{"type":"paragraph","content":[{"text":"Usage:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"bash scripts/validate_role.sh \u003crole-directory>","type":"text"}]},{"type":"paragraph","content":[{"text":"Validates:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Role directory structure (required and recommended directories)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Presence of main.yml files in each directory","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"YAML syntax across all role files","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Ansible syntax using a test playbook","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Best practices with ansible-lint","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Molecule test configuration","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"validate_role_security.sh","type":"text","marks":[{"type":"strong"}]},{"text":" - Security validation script for Ansible roles using Checkov. Scans entire role directory for security issues. Automatically installs checkov in a temporary venv if not available. Complements validate_role.sh with security-focused checks.","type":"text"}]},{"type":"paragraph","content":[{"text":"Usage:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"bash scripts/validate_role_security.sh \u003crole-directory>","type":"text"}]},{"type":"paragraph","content":[{"text":"test_role.sh","type":"text","marks":[{"type":"strong"}]},{"text":" - Wrapper script for Molecule testing with automatic dependency installation. If ","type":"text"},{"text":"molecule","type":"text","marks":[{"type":"code_inline"}]},{"text":" is missing, it creates a temporary venv and installs dependencies. Returns exit code ","type":"text"},{"text":"2","type":"text","marks":[{"type":"code_inline"}]},{"text":" for environment/runtime blockers (for example missing Docker/Podman runtime) and exit code ","type":"text"},{"text":"1","type":"text","marks":[{"type":"code_inline"}]},{"text":" for role/test failures.","type":"text"}]},{"type":"paragraph","content":[{"text":"Usage:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"bash scripts/test_role.sh \u003crole-directory> [scenario]","type":"text"}]},{"type":"paragraph","content":[{"text":"scan_secrets.sh","type":"text","marks":[{"type":"strong"}]},{"text":" - Comprehensive secret scanner that uses grep-based pattern matching to detect hardcoded secrets in Ansible files. Complements Checkov security scanning by catching secrets that static analysis may miss, including passwords, API keys, tokens, AWS credentials, and private keys.","type":"text"}]},{"type":"paragraph","content":[{"text":"Usage:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"bash scripts/scan_secrets.sh \u003cplaybook.yml|role-directory|directory>","type":"text"}]},{"type":"paragraph","content":[{"text":"Detects:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Hardcoded passwords and credentials","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"API keys and tokens","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"AWS access keys and secret keys","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Database connection strings with embedded credentials","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Private key content (RSA, OpenSSH, EC, DSA)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"IMPORTANT:","type":"text","marks":[{"type":"strong"}]},{"text":" This script should ALWAYS be run alongside Checkov (","type":"text"},{"text":"validate_*_security.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":") for comprehensive security scanning. Checkov catches SSL/TLS and protocol issues; this script catches hardcoded secrets.","type":"text"}]},{"type":"paragraph","content":[{"text":"check_fqcn.sh","type":"text","marks":[{"type":"strong"}]},{"text":" - Scans Ansible files to identify modules using short names instead of Fully Qualified Collection Names (FQCN). Recommends migration to ","type":"text"},{"text":"ansible.builtin.*","type":"text","marks":[{"type":"code_inline"}]},{"text":" or appropriate collection namespace for better clarity and future compatibility.","type":"text"}]},{"type":"paragraph","content":[{"text":"Usage:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"bash scripts/check_fqcn.sh \u003cplaybook.yml|role-directory|directory>","type":"text"}]},{"type":"paragraph","content":[{"text":"Detects:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ansible.builtin modules (apt, yum, copy, file, template, service, etc.)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"community.general modules (ufw, docker_container, timezone, etc.)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ansible.posix modules (synchronize, acl, firewalld, etc.)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Provides specific migration recommendations with FQCN alternatives.","type":"text"}]},{"type":"paragraph","content":[{"text":"validate_inventory.sh","type":"text","marks":[{"type":"strong"}]},{"text":" - Validates Ansible inventory files and directories. Checks YAML syntax, resolves host/group hierarchy, and flags common structural issues such as plaintext credentials and missing ","type":"text"},{"text":"ansible_connection=local","type":"text","marks":[{"type":"code_inline"}]},{"text":" for localhost entries. Automatically installs ansible in a temporary venv if not available.","type":"text"}]},{"type":"paragraph","content":[{"text":"Usage:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"bash scripts/validate_inventory.sh \u003cinventory-file|inventory-directory>","type":"text"}]},{"type":"paragraph","content":[{"text":"Validation stages:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"YAML syntax check (yamllint) on all inventory YAML files","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Inventory parse — ","type":"text"},{"text":"ansible-inventory --list","type":"text","marks":[{"type":"code_inline"}]},{"text":" to verify host/group resolution","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Host graph — ","type":"text"},{"text":"ansible-inventory --graph","type":"text","marks":[{"type":"code_inline"}]},{"text":" to display group hierarchy","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Structural checks — plaintext passwords, localhost connection settings, group_vars/host_vars presence","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"references/","type":"text"}]},{"type":"paragraph","content":[{"text":"security_checklist.md","type":"text","marks":[{"type":"strong"}]},{"text":" - Comprehensive security validation checklist for Ansible playbooks covering secrets management, privilege escalation, file permissions, and command injection.","type":"text"}]},{"type":"paragraph","content":[{"text":"best_practices.md","type":"text","marks":[{"type":"strong"}]},{"text":" - Ansible coding standards and best practices for playbook organization, variable handling, task naming, idempotency, and documentation.","type":"text"}]},{"type":"paragraph","content":[{"text":"common_errors.md","type":"text","marks":[{"type":"strong"}]},{"text":" - Database of common Ansible errors with detailed solutions and prevention strategies.","type":"text"}]},{"type":"paragraph","content":[{"text":"module_alternatives.md","type":"text","marks":[{"type":"strong"}]},{"text":" - Guide for replacing deprecated modules with current alternatives.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"assets/","type":"text"}]},{"type":"paragraph","content":[{"text":".yamllint","type":"text","marks":[{"type":"strong"}]},{"text":" - Pre-configured yamllint rules for Ansible YAML files.","type":"text"}]},{"type":"paragraph","content":[{"text":".ansible-lint","type":"text","marks":[{"type":"strong"}]},{"text":" - Pre-configured ansible-lint configuration with reasonable rule settings.","type":"text"}]},{"type":"paragraph","content":[{"text":"molecule.yml.template","type":"text","marks":[{"type":"strong"}]},{"text":" - Template molecule configuration for role testing.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Workflow Examples","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Example 1: Validate a Single Playbook","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"User: \"Check if this playbook.yml file is valid\"\n\nSteps:\n1. Run preflight: `bash scripts/setup_tools.sh`\n2. Run wrapper: `bash scripts/validate_playbook.sh playbook.yml`\n3. If inventory is provided, run check mode: `ansible-playbook -i \u003cinventory> playbook.yml --check --diff`\n4. Run security wrappers:\n - `bash scripts/validate_playbook_security.sh playbook.yml`\n - `bash scripts/scan_secrets.sh playbook.yml`\n5. If custom modules are detected, run docs lookup workflow (Context7 first, web fallback)\n6. Report results with PASS/FAIL/BLOCKED/SKIPPED counts and remediation steps","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Example 2: Validate an Ansible Role","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"User: \"Validate my ansible role in ./roles/webserver/\"\n\nSteps:\n1. Run preflight: `bash scripts/setup_tools.sh`\n2. Run role wrapper: `bash scripts/validate_role.sh ./roles/webserver/`\n3. This checks:\n - Role directory structure (tasks/, defaults/, handlers/, meta/, etc.)\n - Required main.yml files\n - YAML syntax with yamllint\n - Ansible syntax with ansible-playbook\n - Best practices with ansible-lint\n - Molecule configuration (if present)\n4. If `molecule/` exists, attempt Molecule automatically:\n - `bash scripts/test_role.sh ./roles/webserver/`\n - Exit `2`: report `Molecule Status: BLOCKED` with reason, continue remaining checks\n - Exit `1`: report `Molecule Status: FAIL` with debugging guidance\n5. Run role security checks:\n - `bash scripts/validate_role_security.sh ./roles/webserver/`\n - `bash scripts/scan_secrets.sh ./roles/webserver/`\n6. If custom modules detected, run documentation lookup workflow\n7. Provide final report with severity, blockers, and rerun actions","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Example 3: Dry-Run Testing for Production","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"User: \"Run playbook in check mode for production servers\"\n\nSteps:\n1. Verify inventory file exists\n2. Run ansible-playbook --check --diff -i production\n3. Analyze check mode output\n4. Highlight tasks that would change\n5. Review handler notifications\n6. Flag any security concerns\n7. Provide recommendation on safety of applying","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Example 4: Understanding Custom Collection Module","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"User: \"I'm using community.postgresql.postgresql_db version 2.3.0, what parameters are available?\"\n\nSteps:\n1. Try Context7 MCP: `mcp__context7__resolve-library-id(\"ansible community.postgresql\")`\n2. If found, query docs with `mcp__context7__query-docs` for `postgresql_db`\n3. If not found, use `web.search_query`: \"ansible community.postgresql version 2.3.0 postgresql_db module documentation\"\n4. Extract module parameters (required vs optional)\n5. Provide examples of common usage patterns\n6. Note any version-specific considerations","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Example 5: Testing Role with Molecule","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"User: \"Test my nginx role with molecule\"\n\nSteps:\n1. Check if molecule is configured in role\n2. Run preflight (`bash scripts/setup_tools.sh`) and confirm Docker/Podman runtime availability\n3. Run `bash scripts/test_role.sh \u003crole-path> [scenario]`\n4. If exit code is `2`, mark Molecule `BLOCKED`, report reason, and continue non-Molecule checks\n5. If exit code is `1`, inspect converge/verify output and report role issues\n6. Analyze idempotency, syntax, and verification outcomes\n7. Suggest improvements and exact rerun command","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Integration with Other Skills","type":"text"}]},{"type":"paragraph","content":[{"text":"This skill works well in combination with:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"k8s-yaml-validator","type":"text","marks":[{"type":"strong"}]},{"text":" - When Ansible manages Kubernetes resources","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"terraform-validator","type":"text","marks":[{"type":"strong"}]},{"text":" - When Ansible and Terraform are used together","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"k8s-debug","type":"text","marks":[{"type":"strong"}]},{"text":" - For debugging infrastructure managed by Ansible","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Notes","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run stages in order: preflight -> syntax -> lint/FQCN -> check mode -> Molecule (when applicable) -> security -> reference routing -> final report.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use wrapper scripts as default execution path; switch to direct commands only when user asks or when wrapper bootstrapping is blocked.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Treat missing dependencies/runtime as ","type":"text"},{"text":"BLOCKED","type":"text","marks":[{"type":"code_inline"}]},{"text":" (not silent skip), and continue with remaining stages.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For every detected issue class, include mapped reference guidance (","type":"text"},{"text":"common_errors","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"best_practices","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"module_alternatives","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"security_checklist","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Always include explicit rerun commands for failed or blocked stages.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Done Criteria","type":"text"}]},{"type":"paragraph","content":[{"text":"This skill execution is complete when:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Preflight status for required tools is reported (","type":"text"},{"text":"ansible","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ansible-lint","type":"text","marks":[{"type":"code_inline"}]},{"text":", and Molecule runtime status when role tests are in scope).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Validation produces deterministic stage outcomes using ","type":"text"},{"text":"PASS","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"FAIL","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"BLOCKED","type":"text","marks":[{"type":"code_inline"}]},{"text":", and ","type":"text"},{"text":"SKIPPED","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Molecule never dead-ends the full validation flow; blocked runtime conditions are reported with fallback language.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Wrapper-vs-direct command choice is explicit and justified.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Reference lookups are tied to the actual error classes found, with concrete remediation guidance.","type":"text"}]}]}]}]},"metadata":{"date":"2026-11-02","name":"ansible-validator","author":"@skillopedia","source":{"stars":331,"repo_name":"cc-devops-skills","origin_url":"https://github.com/akin-ozer/cc-devops-skills/blob/HEAD/devops-skills-plugin/skills/ansible-validator/SKILL.md","repo_owner":"akin-ozer","body_sha256":"7fe3137edee6841cbd4b1714e9674ef0f176ce376e611ec058cbc3570955fad9","cluster_key":"cf1db125d9d5e32d6abd223e61f4c395b7164a390aa7c95cb449634c794e4cdd","clean_bundle":{"format":"clean-skill-bundle-v1","source":"akin-ozer/cc-devops-skills/devops-skills-plugin/skills/ansible-validator/SKILL.md","text_bytes":204324,"source_commit":"276af751e659315aaf56d3ad13d7c26f4e72e28a","skills_sh_rank":10441,"attachment_count":49,"text_attachments":49,"attachment_storage":"skill_attachments","binary_attachments":0},"cluster_size":1,"skill_md_path":"devops-skills-plugin/skills/ansible-validator/SKILL.md","import_metadata":{"date":"2026-11-02","author":"@skillopedia","version":"v1","category":"devops-infrastructure","category_label":"DevOps"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"devops-infrastructure","import_tag":"skills-sh-top20k-2026-11","description":"Validate, lint, audit, or debug Ansible playbooks, roles, inventories, FQCN, tasks."}},"renderedAt":1793676423397}

Ansible Validator Overview Comprehensive toolkit for validating, linting, and testing Ansible playbooks, roles, and collections. This skill provides automated workflows for ensuring Ansible code quality, syntax validation, dry-run testing with check mode and molecule, and intelligent documentation lookup for custom modules and collections with version awareness. Default behavior: When validating any Ansible role with a directory, attempt Molecule automatically using . If Molecule cannot run due to environment/runtime limits, mark Molecule as , report why, and continue all non-Molecule validat…