claude.md (6538B)
1 # Ansible macOS QA Workstation Provisioning 2 3 ## Project Overview 4 5 Ansible playbook and roles for provisioning a MacBook Pro for a QA analyst. 6 Targets macOS (Apple Silicon and Intel compatible). Uses Ansible roles with 7 proper plugin-based task modules wherever possible — avoid `command`/`shell` 8 one-offs unless there is no suitable module. 9 10 --- 11 12 ## Target Environment 13 14 - **OS**: macOS (Sonoma or later) 15 - **User**: QA analyst 16 - **Connection**: `local` (runs on the Mac itself via `ansible-playbook --connection=local`) 17 - **Python**: System Python or Homebrew Python; do not assume a specific path 18 19 --- 20 21 ## Role Structure 22 23 Organize as individual roles under `roles/`. Each role is self-contained with 24 its own `tasks/`, `defaults/`, `vars/`, `handlers/`, and `meta/` directories 25 as needed. 26 27 ``` 28 roles/ 29 homebrew/ 30 xcode_cli/ 31 chrome/ 32 iterm2/ 33 powerlevel10k/ 34 ``` 35 36 Entry point: `site.yml` applies all roles in order. 37 38 --- 39 40 ## Roles — Requirements and Constraints 41 42 ### homebrew 43 44 - Use the [`community.general.homebrew`](https://docs.ansible.com/ansible/latest/collections/community/general/homebrew_module.html) module for all package installs. 45 - Use [`community.general.homebrew_cask`](https://docs.ansible.com/ansible/latest/collections/community/general/homebrew_cask_module.html) for cask installs. 46 - Bootstrap Homebrew itself only if it is not already installed. Use a 47 `stat` module check on `/opt/homebrew/bin/brew` (Apple Silicon) and 48 `/usr/local/bin/brew` (Intel) to detect presence before bootstrapping. 49 - Bootstrapping Homebrew requires a `command` task (the install script); 50 this is acceptable as a narrow exception since no plugin covers it. 51 - Set `HOMEBREW_NO_AUTO_UPDATE=1` in the environment for install tasks to 52 keep runs fast and deterministic. 53 - Tap `homebrew/cask` if not already present using `community.general.homebrew_tap`. 54 55 ### xcode_cli 56 57 - Install Xcode Command Line Tools. 58 - Check for existing install using the `stat` module on 59 `/Library/Developer/CommandLineTools/usr/bin/git`. 60 - If absent, trigger install via `command: xcode-select --install` — this is 61 acceptable since no module covers CLT installation. 62 - After triggering, wait for completion using a `wait_for` or loop on the 63 `stat` check with `retries` and `delay`. Document that this step may 64 require user interaction on a fresh machine. 65 - Register the result and skip if already installed. 66 67 ### chrome 68 69 - Install Google Chrome via `community.general.homebrew_cask` with 70 `name: google-chrome`. 71 - Ensure the role is idempotent: running it twice should produce no changes 72 the second time. 73 74 ### iterm2 75 76 - Install iTerm2 via `community.general.homebrew_cask` with `name: iterm2`. 77 - Idempotent same as chrome role. 78 - Optionally apply a default preferences plist if a `files/com.googlecode.iterm2.plist` 79 is present in the role — use the `ansible.builtin.copy` module to place it 80 at `~/Library/Preferences/com.googlecode.iterm2.plist`. 81 82 ### powerlevel10k 83 84 - Install the `powerlevel10k` theme for Zsh using Homebrew: 85 - Tap `romkatv/powerlevel10k` via `community.general.homebrew_tap`. 86 - Install `powerlevel10k` via `community.general.homebrew`. 87 - Use `ansible.builtin.lineinfile` to add the sourcing line to `~/.zshrc`: 88 ``` 89 source $(brew --prefix)/opt/powerlevel10k/powerlevel10k.zsh-theme 90 ``` 91 Use `regexp` to make this idempotent (do not add duplicate lines). 92 - Do **not** attempt to run the interactive `p10k configure` wizard — document 93 in comments that the user must run this manually on first login. 94 - Optionally copy a `files/.p10k.zsh` into `~/` using `ansible.builtin.copy` 95 if the file exists in the role, so a pre-baked config can be committed. 96 97 --- 98 99 ## Module / Plugin Preferences 100 101 | Task Type | Preferred Module | Avoid | 102 |------------------------|-------------------------------------------|--------------------| 103 | Install brew packages | `community.general.homebrew` | `command: brew` | 104 | Install cask apps | `community.general.homebrew_cask` | `command: brew` | 105 | Manage taps | `community.general.homebrew_tap` | `command: brew tap`| 106 | File presence check | `ansible.builtin.stat` | `command: ls` | 107 | Edit shell config | `ansible.builtin.lineinfile` | `command: echo >>` | 108 | Copy config files | `ansible.builtin.copy` | `command: cp` | 109 | Directory creation | `ansible.builtin.file` (state: directory) | `command: mkdir` | 110 | Conditional skips | `when: result.stat.exists` | — | 111 112 --- 113 114 ## Collections 115 116 Declare required collections in `requirements.yml`: 117 118 ```yaml 119 collections: 120 - name: community.general 121 version: ">=8.0.0" 122 ``` 123 124 Install before running: `ansible-galaxy collection install -r requirements.yml` 125 126 --- 127 128 ## Inventory 129 130 Use a minimal local inventory. Suggested `inventory/local.ini`: 131 132 ```ini 133 [mac] 134 localhost ansible_connection=local ansible_python_interpreter=/usr/bin/python3 135 ``` 136 137 --- 138 139 ## Variables 140 141 Define sensible defaults in each role's `defaults/main.yml`. Example for 142 `homebrew` role: 143 144 ```yaml 145 homebrew_prefix_arm: /opt/homebrew 146 homebrew_prefix_intel: /usr/local 147 homebrew_packages: [] 148 homebrew_casks: [] 149 ``` 150 151 The top-level `group_vars/all.yml` can set QA-specific packages if needed. 152 153 --- 154 155 ## Idempotency Requirements 156 157 Every task must be idempotent. Running the playbook multiple times must 158 produce zero changes after the first successful run. Use: 159 160 - `stat` + `when: not result.stat.exists` guards for bootstrapping steps 161 - Module-native idempotency for Homebrew tasks (modules handle this) 162 - `lineinfile` with `regexp` for shell config edits 163 164 --- 165 166 ## Error Handling 167 168 - Use `ignore_errors: false` (default) — fail loudly. 169 - For the Xcode CLT step, note in a comment that the task may time out on a 170 fresh machine if the user does not click through the GUI dialog. Consider 171 adding a `retries: 30 / delay: 10` loop on the stat check post-trigger. 172 173 --- 174 175 ## Running the Playbook 176 177 ```bash 178 # Install dependencies 179 ansible-galaxy collection install -r requirements.yml 180 181 # Dry run 182 ansible-playbook -i inventory/local.ini site.yml --check --diff 183 184 # Apply 185 ansible-playbook -i inventory/local.ini site.yml 186 ``` 187 188 --- 189 190 ## Out of Scope 191 192 - Managing macOS system preferences via `defaults write` (can be a follow-on role) 193 - Configuring SSH keys or Git identity 194 - Installing language runtimes (Node, Python, Ruby) — add roles as needed 195 - Any CI/CD pipeline integration