Ansible code-gen backend for
iac-forge— emits Ansible modules from IR.
ansible-forge is a Rust library that implements the iac_forge::Backend
trait for the Ansible target. Given the IR types from iac-forge —
IacResource, IacAction, IacDataSource, and IacProvider — it emits:
- a Python module per resource / action / data source under
plugins/modules/<name>.py - a shared
plugins/module_utils/akeyless_client.pyhelper holding the SDK binding (auth, body builder, API dispatch) - collection metadata:
galaxy.yml,meta/runtime.yml,requirements.txt, a stubREADME.md - a smoke-test playbook under
tests/integration/targets/<name>/tasks/main.yml
The emitter is consumed by iac-forge-cli
behind iac-forge-cli generate --backend ansible, and ships the
akeyless.akeyless
collection on Galaxy.
Add the crate as a dependency:
[dependencies]
ansible-forge = "0.1"
iac-forge = "0.1"Then plug it into your generator pipeline:
use ansible_forge::AnsibleBackend;
use iac_forge::{Backend, IacResource, IacProvider};
let backend = AnsibleBackend::new();
let artifacts = backend.generate_resource(&resource, &provider)?;
for artifact in artifacts {
std::fs::write(&artifact.path, &artifact.content)?;
}For one-shot CLI use, install the dispatcher and let it walk the spec set:
cargo install iac-forge-cli
iac-forge-cli generate --backend ansible --output ./outThree emitter variants cover the entire Akeyless surface:
| IR type | File suffix | Pattern |
|---|---|---|
IacResource |
<name>.py |
state: present | absent — drives create / update / delete |
IacAction |
<name>.py |
one-shot RPC, changed: true, sensitive output masking |
IacDataSource |
<name>_info.py |
read-only fetch via the matching list/get API method |
All modules share a single plugins/module_utils/akeyless_client.py:
get_client(module)— resolves auth from params / env / pre-issued token, returns(akeyless.V2Api, token).build_body(model_class_name, params)— filtersNones and unknown keys, instantiatesakeyless.<Model>.call_api(module, client, method_name, body)— invokes the SDK method, normalizes the response.
akeyless-terraform-resources/resources/*.toml ← spec source
→ iac-forge ResourceSpec parser
→ iac-forge IR (IacResource / IacAction / IacDataSource)
→ ansible-forge AnsibleBackend
→ plugins/modules/*.py + collection metadata
→ ansible-galaxy collection build
→ galaxy.ansible.com
The hand-off boundary is the IR — iac-forge owns parsing and shape
inference, ansible-forge owns Python emission. Adding a new IR shape (the
most recent example: IacAction) means a new dispatch arm and a new
emitter, no parser changes here.
To add a new emitter variant:
- Add the IR type upstream in
iac-forge(e.g.IacAction) and haveiac-forge-clidispatch to a newBackendmethod (e.g.generate_action). - Implement the method on
AnsibleBackendinsrc/backend.rs; delegate body generation to a new function insrc/module_gen.rs. - Add a snapshot test for a representative spec — see the bottom of
module_gen.rs. - The end-to-end test in
tests/integration_toml_walk.rswill walk every TOML spec and assert nothing panics or generates an unparseable Python file.
Idiom-first: stay close to existing patterns, lean on
iac-forge::{NamingConvention, to_snake_case, strip_provider_prefix} for
string transforms, and don't reinvent the IR.
# Unit + snapshot tests
cargo test --lib
# End-to-end: walk all 119+ TOML specs and emit each through the backend
cargo test --test integration_toml_walkThe integration test is what gives us the "208 modules generate without panic" guarantee, and it runs in CI on every push.
For iterative dev against a local iac-forge checkout, use a [patch]
section in Cargo.toml:
[patch."https://github.com/pleme-io/iac-forge"]
iac-forge = { path = "../iac-forge" }Drop the patch before opening a PR.
This crate operates under
Constructive Substrate Engineering.
The Compounding Directive (solve-once, load-bearing fixes, idiom-first,
direction beats velocity) is in the org CLAUDE.md. Read both before
non-trivial changes.
MIT. Copyright (c) 2026 pleme-io.