Date: 2026-05-11
Time: 15:33
This file implements FTL2's module proxy system — the magic behind the ftl.modulename() syntax. It intercepts attribute access on the ftl object to dynamically resolve and execute automation modules, supporting both simple names (ftl.file(...)) and fully qualified collection names (ftl.amazon.aws.ec2instance(...)). It also provides HostScopedProxy, which enables host/group-targeted execution via ftl.webservers.service(...). The proxy layer handles module exclusion checks, shadowed (native) module resolution, become/privilege escalation, and audit tracking.
Basic module calls — modules are called as async methods on the ftl object:
await ftl.file(path="/tmp/test", state="touch")
await ftl.amazon.aws.ec2_instance(instance_type="t3.micro")
Host-scoped execution — target specific hosts or groups:
await ftl.webservers.service(name="nginx", state="restarted")
await ftl.web01.file(path="/tmp/test", state="touch")
Native shadowed modules — several Ansible modules have FTL2-native replacements built directly into HostScopedProxy:
waitforssh(timeout, delay, sleep, connect_timeout) — polls TCP connection until SSH is availableping() — tests the full FTL2 gate execution pipeline (TCP → SSH → gate → command → response)copy(src, dest, content, mode, owner, group, backup) — SFTP file transfer with idempotencytemplate(src, dest, **variables) — Jinja2 render + SFTP transferfetch(src, dest, flat) — pull remote file to controllershell(cmd, chdir, creates, removes, executable, stdin, timeout) — shell command execution with pipes/redirects
# Dynamic host provisioning pattern
ftl.add_host("minecraft-9", ansible_host=ip)
await ftl.minecraft_9.wait_for_ssh(timeout=120, delay=10)
await ftl.minecraft_9.copy(src="server.properties", dest="/opt/mc/server.properties")
| Parameter | Module | Default | Notes |
|-----------|--------|---------|-------|
| timeout | waitforssh | 600 | Max seconds to wait |
| delay | waitforssh | 0 | Seconds before first check |
| sleep | waitforssh | 1 | Seconds between retries |
| connecttimeout | waitfor_ssh | 5 | Per-attempt timeout |
| become | copy, template, shell | None | Enable privilege escalation |
| become_user | copy, template, shell | None | Target user for escalation |
| flat | fetch | False | If False, creates dest/hostname/src directory structure |
| creates | shell | None | Skip if path exists (idempotency) |
| removes | shell | None | Skip if path does NOT exist |
| executable | shell | /bin/sh | Shell interpreter |
| backup | copy | False | Create timestamped backup before overwrite |
Become/privilege escalation flows through becomeoverrides dict and per-host become_config. When become is active, copy uses a temp-file-then-sudo-mv pattern instead of direct SFTP writes.
checkexcluded() raises ExcludedModuleError before any module runs if the module is on the exclusion list.isshadowed() / getnative_method() detect when a native FTL2 implementation should replace a bundled Ansible module.copy compares file content before writing; shell supports creates/removes guards. Unchanged files report changed: False.local/localhost targets and use direct local operations (subprocess, pathlib) instead of SSH/SFTP.copy uses asyncio.gather to transfer to all hosts in a group concurrently./tmp/.ftl2copy* then sudo mv'd to the destination, since the SSH user may lack direct write access.trackresult() to register results in the context's audit/summary pipeline, including duration, changed status, and per-host output.ping() tests the entire gate execution pipeline end-to-end, not just the connection plugin.copy() method — template variables are passed as **kwargs.ftl2.automation.become (become override extraction), ftl2.exceptions (ExcludedModuleError, FTL2ConnectionError), ftl2.module_loading.excluded / shadowed (module filtering)AutomationContext for execution (execute, runon, getsshconnection, hosts, _results), BecomeConfig for privilege escalationModuleProxy (not shown in truncated code) delegates to HostScopedProxy when attribute access resolves to a host/group nameftl2.ftl_modules.executor.ExecuteResult for result tracking, Jinja2 for template renderingcontext.hosts, and the audit pipeline via context._results