Updating the Ansible Code
The YAML editor is where the plugin actually does its work. Plugin YAML in Rogue Arena is standard Ansible with a few load-bearing rules that diverge from how you’d write a free-standing playbook. Read this page carefully — the differences are small but unforgiving, and most first-build failures come from missing one of them.
Rule 1 — Task List Only, No Playbook Header
Section titled “Rule 1 — Task List Only, No Playbook Header”This is the single biggest difference between Rogue Arena plugins and a normal Ansible playbook. Your YAML is a flat task list. The platform wraps it in a play at runtime (sets hosts, vars, and the tasks: collection). You do not write the wrapper.
Correct — start directly with - name: tasks:
- name: Create staging dir ansible.windows.win_file: path: "C:\\PluginSetup" state: directory
- name: Stage installer from vault ansible.windows.win_copy: src: my-installer.exe dest: "C:\\PluginSetup\\my-installer.exe"Wrong — do not include playbook structure:
---- hosts: all tasks: - name: Create staging dir ...No --- at the top. No - hosts: line. No outer tasks: key. If you paste a snippet from the public Ansible docs that includes a playbook header, strip it before saving.
Inline execution only: you cannot use import_tasks, include_tasks, or reference separate YAML files. Everything lives in one continuous task list.
Rule 2 — Stage From the Vault Before You Install
Section titled “Rule 2 — Stage From the Vault Before You Install”Non-apt resources (.exe, .msi, .deb, Docker images, git tarballs, ISOs, etc.) live in the plugin vault — uploaded once during development, then pulled onto the target VM at runtime. The vault is managed via the Resources panel.
- name: Stage VLC installer from plugin vault ansible.windows.win_copy: src: vlc-3.0.20-win64.exe dest: "C:\\PluginSetup\\vlc-3.0.20-win64.exe"For copies on the target VM later in the play — mirroring a config to a second path, moving a staged file into place, duplicating something machine-internally — use a shell-level copy instead:
- Windows:
Copy-Iteminsideansible.windows.win_powershellorwin_shell - Linux:
cp/mvinsideansible.builtin.shell
For movement between two VMs, use a shell command on the source VM (robocopy, scp, Copy-Item over a UNC path).
Apt is the runtime exception. Rogue Arena ships an apt mirror inside every Architect scenario, with most popular repositories already wired up. On Linux machines, just apt install whatever you need — no repo-adding boilerplate:
- name: Install Docker apt: pkg: - docker-ce - docker-ce-cli - containerd.io state: present update_cache: yesThe vault is for things the mirror doesn’t have. Public-internet downloads (wget, curl, Invoke-WebRequest to the open internet) at runtime are a failure state — fetch them into the vault during develop instead.
Rule 3 — Write Text Files Inline Via content:
Section titled “Rule 3 — Write Text Files Inline Via content:”Scripts, configs, and other text content don’t need to live in the vault. Use win_copy / copy with a content: block to write them straight onto the target:
- name: Write PowerShell setup script ansible.windows.win_copy: content: | $ErrorActionPreference = 'Stop' Write-Host "Running setup" # ... dest: 'C:\PluginSetup\setup.ps1'- name: Write bash setup script ansible.builtin.copy: content: | #!/bin/bash set -e echo "Running setup" dest: /tmp/plugin-setup/setup.sh mode: '0755'Reserve the vault for binaries — keep text in YAML.
Rule 4 — Use the Right Module Collection
Section titled “Rule 4 — Use the Right Module Collection”Windows modules live in two collections, and the wrong namespace fails the build:
ansible.windows.*— core Windows modules (bundled with Ansible)community.windows.*— extended Windows modules (community-maintained)
Common mistakes:
| Wrong | Right |
|---|---|
ansible.windows.win_lineinfile | community.windows.win_lineinfile |
ansible.windows.win_firewall_rule | community.windows.win_firewall_rule |
ansible.windows.win_unzip | community.windows.win_unzip |
Always use the fully qualified collection name (FQCN). Platform mapping at a glance:
| Windows | Linux |
|---|---|
ansible.windows.win_copy | ansible.builtin.copy |
ansible.windows.win_file | ansible.builtin.file |
ansible.windows.win_stat | ansible.builtin.stat |
ansible.windows.win_shell | ansible.builtin.shell |
ansible.windows.win_powershell | n/a (use shell) |
ansible.windows.win_reboot | ansible.builtin.reboot |
ansible.windows.win_service | ansible.builtin.service |
Rule 5 — Windows Paths Use Doubled Backslashes
Section titled “Rule 5 — Windows Paths Use Doubled Backslashes”In YAML double-quoted strings, single backslashes are escape sequences (\t = tab, \n = newline, \V = parse error). House style is double-quoted Windows paths with \\:
- name: Check file ansible.windows.win_stat: path: "C:\\Program Files\\MyApp\\app.exe"Exception — inside script: | blocks, content passes raw to PowerShell. Use normal single-backslash paths:
- name: PowerShell script ansible.windows.win_powershell: script: | $path = "C:\scripts\file.exe" Copy-Item -Path $path -Destination "C:\temp\"Rule 6 — Default to No Privilege Escalation
Section titled “Rule 6 — Default to No Privilege Escalation”Plugins run as SYSTEM on Windows and root on Linux out of the box. Do not use become, ansible_become, or runas unless you specifically need to drop into a user’s context.
You do NOT need become for (this list is huge — internalize it):
- Writing files to system paths (
C:\,C:\temp,/etc/,/opt/, etc.) - Modifying
HKLM(HKEY_LOCAL_MACHINE) registry - Installing software system-wide
- Managing services
- Creating directories anywhere on the system
- Active Directory operations from a domain-joined machine (SYSTEM has domain access)
You DO need become/runas for (narrow cases):
- Modifying user-specific registry hives (
HKCU— HKEY_CURRENT_USER) - Touching user profile folders that SYSTEM can’t read
- Running processes that must appear in a user’s session context
- AD operations that require a specific user identity (Domain Admin, etc.)
Example — HKCU registry edit (the rare case where runas is needed):
- name: Change user wallpaper ansible.windows.win_powershell: script: | Set-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name Wallpaper -Value "C:\wallpaper.jpg" RUNDLL32.EXE user32.dll,UpdatePerUserSystemParameters vars: ansible_become: true ansible_become_method: runas ansible_become_user: "{{ username }}" ansible_become_password: "{{ user_password }}"Example — AD operation that needs a Domain Admin token:
- name: Run as Domain Admin ansible.windows.win_powershell: script: | Import-Module ActiveDirectory # ... AD operations vars: ansible_become: true ansible_become_method: runas ansible_become_user: "Administrator@{{ DomainNameFQDN }}" ansible_become_password: "{{ domain_admin_password }}"Rule 7 — Normalize CSV Inputs
Section titled “Rule 7 — Normalize CSV Inputs”CSV parameters arrive as raw strings that often carry a BOM and Windows carriage returns. Strip them up front:
- name: Normalize CSV ansible.builtin.set_fact: my_csv_clean: "{{ my_csv_param | default('') | regex_replace('^\\ufeff','') | regex_replace('\\r','') | trim }}"
- name: Parse CSV to list of objects ansible.builtin.set_fact: my_records: "{{ my_csv_clean | community.general.from_csv }}"For complex processing (bulk AD creation, etc.), write the cleaned CSV to a file and let PowerShell Import-Csv chew on it.
Rule 8 — Always Validate at the End
Section titled “Rule 8 — Always Validate at the End”A green build doesn’t mean the install worked. After every install / configuration step, drop in checks:
- name: Check application installed ansible.windows.win_stat: path: "C:\\Program Files\\MyApp\\app.exe" register: app_check
- name: Fail if not found ansible.builtin.fail: msg: "Installation failed — application not found" when: not app_check.stat.existsService checks, port polls, retry-until-condition — same idea. A plugin that finishes silently while leaving the box half-configured is the worst kind of bug.
Rule 9 — Idempotency With changed_when
Section titled “Rule 9 — Idempotency With changed_when”Track whether operations actually changed anything. PowerShell scripts emit JSON with a changed flag; Ansible reads it via changed_when:
- name: Configure setting (idempotent) ansible.windows.win_powershell: script: | $changed = $false if ($currentValue -ne $desiredValue) { Set-Something -Value $desiredValue $changed = $true } [pscustomobject]@{ changed = $changed } | ConvertTo-Json register: config_result changed_when: config_result.output is search('"changed"\s*:\s*true')Re-running a finished plugin should be a no-op. If your second run reports changes, you have a bug.
Complete Example — Simple App Install
Section titled “Complete Example — Simple App Install”The canonical pattern stitched together — stage from vault, install, cleanup, validate. (Some plugins also drop an opening set_fact block to centralize version numbers and staging paths — optional but common.)
- name: Create staging folder ansible.windows.win_file: path: "C:\\PluginSetup" state: directory
- name: Stage VLC installer from plugin vault ansible.windows.win_copy: src: vlc-3.0.20-win64.exe dest: "C:\\PluginSetup\\vlc-3.0.20-win64.exe"
- name: Install VLC silently ansible.windows.win_powershell: script: | C:\PluginSetup\vlc-3.0.20-win64.exe /L=1033 /S
- name: Pause to let VLC setup finish ansible.builtin.pause: minutes: 4
- name: Remove staging folder ansible.windows.win_file: path: "C:\\PluginSetup" state: absent
- name: Check for VLC executable ansible.windows.win_stat: path: "C:\\Program Files\\VideoLAN\\VLC\\vlc.exe" register: vlc_check
- name: Fail if VLC is missing ansible.builtin.fail: msg: "Validation failed — VLC executable not found" when: not vlc_check.stat.existsThat’s the shape — every plugin worth shipping follows it.
When in Doubt — Clone and Read
Section titled “When in Doubt — Clone and Read”The Plugin Browser exposes the YAML of every plugin you can see. When you’re stuck, pick a published plugin that does something similar, clone it, and read its task list. Three or four reference plugins and you’ll have the patterns internalized.
Or Hand It to Claude
Section titled “Or Hand It to Claude”If hand-authoring YAML isn’t your favorite use of time, the Claude MCP plugin ships two skills tailored to plugin work:
/rogue-plugin-brainstorm— start a new plugin from scratch/rogue-plugin-develop— iterate on an existing plugin
Both skills know every rule on this page and produce YAML that drops straight into the Plugin Dev editor.
The real magic shows up on the hard plugins — multi-machine server/client stacks like Elastic (Stack server on one box, Agent on every endpoint), Splunk (indexer + forwarders), AD forest builds with trust relationships, Wireguard mesh deployments, anything where a half-dozen plays have to land in the right order across the right boxes with the right parameters. Claude handles those end-to-end — researches install flows, splits the work across plugins, wires up the parent/child relationships, drops vault files where they need to go, validates the offline install, and iterates against live deploy logs when something breaks. The kind of plugin that takes a weekend of hand-authoring gets stitched together in a single afternoon.