feat: initial Claude Code configuration scaffold

Comprehensive Claude Code guidance system with:

- 5 agents: tdd-guardian, code-reviewer, security-scanner, refactor-scan, dependency-audit
- 18 skills covering languages (Python, TypeScript, Rust, Go, Java, C#),
  infrastructure (AWS, Azure, GCP, Terraform, Ansible, Docker/K8s, Database, CI/CD),
  testing (TDD, UI, Browser), and patterns (Monorepo, API Design, Observability)
- 3 hooks: secret detection, auto-formatting, TDD git pre-commit
- Strict TDD enforcement with 80%+ coverage requirements
- Multi-model strategy: Opus for planning, Sonnet for execution (opusplan)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-20 15:47:34 -05:00
commit befb8fbaeb
34 changed files with 12233 additions and 0 deletions

View File

@@ -0,0 +1,632 @@
---
name: ansible-automation
description: Ansible configuration management with playbook patterns, roles, and best practices. Use when writing Ansible playbooks, roles, or inventory configurations.
---
# Ansible Automation Skill
## Project Structure
```
ansible/
├── ansible.cfg
├── inventory/
│ ├── dev/
│ │ ├── hosts.yml
│ │ └── group_vars/
│ │ ├── all.yml
│ │ └── webservers.yml
│ ├── staging/
│ └── prod/
├── playbooks/
│ ├── site.yml # Main playbook
│ ├── webservers.yml
│ ├── databases.yml
│ └── deploy.yml
├── roles/
│ ├── common/
│ ├── nginx/
│ ├── postgresql/
│ └── app/
├── group_vars/
│ └── all.yml
├── host_vars/
└── files/
```
## Configuration (ansible.cfg)
```ini
[defaults]
inventory = inventory/dev/hosts.yml
roles_path = roles
remote_user = ec2-user
host_key_checking = False
retry_files_enabled = False
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 86400
# Security
no_log = False
display_skipped_hosts = False
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False
[ssh_connection]
pipelining = True
control_path = /tmp/ansible-ssh-%%h-%%p-%%r
```
## Inventory Patterns
### YAML Inventory (recommended)
```yaml
# inventory/dev/hosts.yml
all:
children:
webservers:
hosts:
web1:
ansible_host: 10.0.1.10
web2:
ansible_host: 10.0.1.11
vars:
nginx_port: 80
app_port: 8000
databases:
hosts:
db1:
ansible_host: 10.0.2.10
postgresql_version: "15"
workers:
hosts:
worker[1:3]:
ansible_host: "10.0.3.{{ item }}"
vars:
ansible_user: ec2-user
ansible_python_interpreter: /usr/bin/python3
```
### Dynamic Inventory (AWS)
```yaml
# inventory/aws_ec2.yml
plugin: amazon.aws.aws_ec2
regions:
- eu-west-2
filters:
tag:Environment: dev
instance-state-name: running
keyed_groups:
- key: tags.Role
prefix: role
- key: placement.availability_zone
prefix: az
hostnames:
- private-ip-address
compose:
ansible_host: private_ip_address
```
## Playbook Patterns
### Main Site Playbook
```yaml
# playbooks/site.yml
---
- name: Configure all hosts
hosts: all
become: true
roles:
- common
- name: Configure web servers
hosts: webservers
become: true
roles:
- nginx
- app
- name: Configure databases
hosts: databases
become: true
roles:
- postgresql
```
### Application Deployment
```yaml
# playbooks/deploy.yml
---
- name: Deploy application
hosts: webservers
become: true
serial: "25%" # Rolling deployment
max_fail_percentage: 25
vars:
app_version: "{{ lookup('env', 'APP_VERSION') | default('latest') }}"
pre_tasks:
- name: Verify deployment prerequisites
ansible.builtin.assert:
that:
- app_version is defined
- app_version != ''
fail_msg: "APP_VERSION must be set"
- name: Remove from load balancer
ansible.builtin.uri:
url: "{{ lb_api_url }}/deregister"
method: POST
body:
instance_id: "{{ ansible_hostname }}"
body_format: json
delegate_to: localhost
when: lb_api_url is defined
roles:
- role: app
vars:
app_state: present
post_tasks:
- name: Wait for application health check
ansible.builtin.uri:
url: "http://localhost:{{ app_port }}/health"
status_code: 200
register: health_check
until: health_check.status == 200
retries: 30
delay: 5
- name: Add back to load balancer
ansible.builtin.uri:
url: "{{ lb_api_url }}/register"
method: POST
body:
instance_id: "{{ ansible_hostname }}"
body_format: json
delegate_to: localhost
when: lb_api_url is defined
handlers:
- name: Restart application
ansible.builtin.systemd:
name: myapp
state: restarted
daemon_reload: true
```
## Role Structure
### Role Layout
```
roles/app/
├── defaults/
│ └── main.yml # Default variables (lowest priority)
├── vars/
│ └── main.yml # Role variables (higher priority)
├── tasks/
│ ├── main.yml # Main task entry point
│ ├── install.yml
│ ├── configure.yml
│ └── service.yml
├── handlers/
│ └── main.yml # Handlers for notifications
├── templates/
│ ├── app.conf.j2
│ └── systemd.service.j2
├── files/
│ └── scripts/
├── meta/
│ └── main.yml # Role metadata and dependencies
└── README.md
```
### Role Tasks
```yaml
# roles/app/tasks/main.yml
---
- name: Include installation tasks
ansible.builtin.include_tasks: install.yml
tags:
- install
- name: Include configuration tasks
ansible.builtin.include_tasks: configure.yml
tags:
- configure
- name: Include service tasks
ansible.builtin.include_tasks: service.yml
tags:
- service
```
```yaml
# roles/app/tasks/install.yml
---
- name: Create application user
ansible.builtin.user:
name: "{{ app_user }}"
system: true
shell: /bin/false
home: "{{ app_home }}"
create_home: true
- name: Create application directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ app_user }}"
group: "{{ app_group }}"
mode: "0755"
loop:
- "{{ app_home }}"
- "{{ app_home }}/releases"
- "{{ app_home }}/shared"
- "{{ app_log_dir }}"
- name: Download application artifact
ansible.builtin.get_url:
url: "{{ app_artifact_url }}/{{ app_version }}/app.tar.gz"
dest: "{{ app_home }}/releases/{{ app_version }}.tar.gz"
checksum: "sha256:{{ app_checksum }}"
register: download_result
- name: Extract application
ansible.builtin.unarchive:
src: "{{ app_home }}/releases/{{ app_version }}.tar.gz"
dest: "{{ app_home }}/releases/{{ app_version }}"
remote_src: true
when: download_result.changed
- name: Link current release
ansible.builtin.file:
src: "{{ app_home }}/releases/{{ app_version }}"
dest: "{{ app_home }}/current"
state: link
notify: Restart application
```
### Role Handlers
```yaml
# roles/app/handlers/main.yml
---
- name: Restart application
ansible.builtin.systemd:
name: "{{ app_service_name }}"
state: restarted
daemon_reload: true
- name: Reload nginx
ansible.builtin.systemd:
name: nginx
state: reloaded
```
### Role Defaults
```yaml
# roles/app/defaults/main.yml
---
app_user: myapp
app_group: myapp
app_home: /opt/myapp
app_port: 8000
app_log_dir: /var/log/myapp
app_service_name: myapp
# These should be overridden
app_version: ""
app_artifact_url: ""
app_checksum: ""
```
## Templates (Jinja2)
### Application Config
```jinja2
{# roles/app/templates/app.conf.j2 #}
# Application Configuration
# Managed by Ansible - DO NOT EDIT
[server]
host = {{ app_bind_host | default('0.0.0.0') }}
port = {{ app_port }}
workers = {{ app_workers | default(ansible_processor_vcpus * 2) }}
[database]
host = {{ db_host }}
port = {{ db_port | default(5432) }}
name = {{ db_name }}
user = {{ db_user }}
# Password from environment variable
password_env = DB_PASSWORD
[logging]
level = {{ app_log_level | default('INFO') }}
file = {{ app_log_dir }}/app.log
{% if app_features is defined %}
[features]
{% for feature, enabled in app_features.items() %}
{{ feature }} = {{ enabled | lower }}
{% endfor %}
{% endif %}
```
### Systemd Service
```jinja2
{# roles/app/templates/systemd.service.j2 #}
[Unit]
Description={{ app_description | default('Application Service') }}
After=network.target
Wants=network-online.target
[Service]
Type=simple
User={{ app_user }}
Group={{ app_group }}
WorkingDirectory={{ app_home }}/current
ExecStart={{ app_home }}/current/bin/app serve
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=5
# Environment
Environment="PORT={{ app_port }}"
Environment="LOG_LEVEL={{ app_log_level | default('INFO') }}"
EnvironmentFile=-{{ app_home }}/shared/.env
# Security
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths={{ app_log_dir }} {{ app_home }}/shared
[Install]
WantedBy=multi-user.target
```
## Secrets Management with Vault
### Encrypting Variables
```bash
# Create encrypted file
ansible-vault create group_vars/prod/vault.yml
# Edit encrypted file
ansible-vault edit group_vars/prod/vault.yml
# Encrypt existing file
ansible-vault encrypt group_vars/prod/secrets.yml
# Encrypt string for inline use
ansible-vault encrypt_string 'mysecret' --name 'db_password'
```
### Vault Variables Pattern
```yaml
# group_vars/prod/vault.yml (encrypted)
vault_db_password: "supersecretpassword"
vault_api_key: "api-key-here"
# group_vars/prod/vars.yml (plain, references vault)
db_password: "{{ vault_db_password }}"
api_key: "{{ vault_api_key }}"
```
### Using Vault in Playbooks
```bash
# Run with vault password file
ansible-playbook playbooks/site.yml --vault-password-file ~/.vault_pass
# Run with vault password prompt
ansible-playbook playbooks/site.yml --ask-vault-pass
# Multiple vault IDs
ansible-playbook playbooks/site.yml \
--vault-id dev@~/.vault_pass_dev \
--vault-id prod@~/.vault_pass_prod
```
## Idempotency Best Practices
```yaml
# GOOD: Idempotent - can run multiple times safely
- name: Ensure package is installed
ansible.builtin.apt:
name: nginx
state: present
- name: Ensure service is running
ansible.builtin.systemd:
name: nginx
state: started
enabled: true
- name: Ensure configuration file exists
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
mode: "0644"
notify: Reload nginx
# BAD: Not idempotent - will fail on second run
- name: Add line to file
ansible.builtin.shell: echo "export PATH=/app/bin:$PATH" >> /etc/profile
# Use lineinfile instead!
# GOOD: Idempotent alternative
- name: Add application to PATH
ansible.builtin.lineinfile:
path: /etc/profile.d/app.sh
line: 'export PATH=/app/bin:$PATH'
create: true
mode: "0644"
```
## Error Handling
```yaml
- name: Deploy with error handling
block:
- name: Download artifact
ansible.builtin.get_url:
url: "{{ artifact_url }}"
dest: /tmp/artifact.tar.gz
- name: Extract artifact
ansible.builtin.unarchive:
src: /tmp/artifact.tar.gz
dest: /opt/app
remote_src: true
rescue:
- name: Log deployment failure
ansible.builtin.debug:
msg: "Deployment failed on {{ inventory_hostname }}"
- name: Send alert
ansible.builtin.uri:
url: "{{ slack_webhook }}"
method: POST
body:
text: "Deployment failed on {{ inventory_hostname }}"
body_format: json
delegate_to: localhost
always:
- name: Clean up temporary files
ansible.builtin.file:
path: /tmp/artifact.tar.gz
state: absent
```
## Conditionals and Loops
```yaml
# Conditional execution
- name: Install package (Debian)
ansible.builtin.apt:
name: nginx
state: present
when: ansible_os_family == "Debian"
- name: Install package (RedHat)
ansible.builtin.yum:
name: nginx
state: present
when: ansible_os_family == "RedHat"
# Loops
- name: Create users
ansible.builtin.user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
state: present
loop:
- { name: deploy, groups: [wheel, docker] }
- { name: monitoring, groups: [wheel] }
# Loop with dict
- name: Configure services
ansible.builtin.systemd:
name: "{{ item.key }}"
state: "{{ item.value.state }}"
enabled: "{{ item.value.enabled }}"
loop: "{{ services | dict2items }}"
vars:
services:
nginx:
state: started
enabled: true
postgresql:
state: started
enabled: true
```
## Commands
```bash
# Syntax check
ansible-playbook playbooks/site.yml --syntax-check
# Dry run (check mode)
ansible-playbook playbooks/site.yml --check
# Dry run with diff
ansible-playbook playbooks/site.yml --check --diff
# Run playbook
ansible-playbook playbooks/site.yml
# Run with specific inventory
ansible-playbook -i inventory/prod/hosts.yml playbooks/site.yml
# Limit to specific hosts
ansible-playbook playbooks/site.yml --limit webservers
# Run specific tags
ansible-playbook playbooks/site.yml --tags "configure,service"
# Skip tags
ansible-playbook playbooks/site.yml --skip-tags "install"
# Extra variables
ansible-playbook playbooks/deploy.yml -e "app_version=1.2.3"
# Ad-hoc commands
ansible webservers -m ping
ansible all -m shell -a "uptime"
ansible databases -m service -a "name=postgresql state=restarted" --become
```
## Anti-Patterns to Avoid
```yaml
# BAD: Using shell when module exists
- name: Install package
ansible.builtin.shell: apt-get install -y nginx
# GOOD: Use the appropriate module
- name: Install package
ansible.builtin.apt:
name: nginx
state: present
# BAD: Hardcoded values
- name: Create user
ansible.builtin.user:
name: deploy
uid: 1001
# GOOD: Use variables
- name: Create user
ansible.builtin.user:
name: "{{ deploy_user }}"
uid: "{{ deploy_uid | default(omit) }}"
# BAD: Secrets in plain text
- name: Set database password
ansible.builtin.lineinfile:
path: /etc/app/config
line: "DB_PASSWORD=mysecret" # NEVER!
# GOOD: Use vault
- name: Set database password
ansible.builtin.lineinfile:
path: /etc/app/config
line: "DB_PASSWORD={{ vault_db_password }}"
```