Building a Backend
A CRADLE backend translates resolved Deployment IR into files for a target testbed. It remains separate from the CRADLE language and compiler, so adding a target does not require changing scenario syntax or rebuilding CradleXC.
Treat the JSON produced by cxc dump-ir as the backend's source data. The
worked translator on this page starts from that JSON, validates its references
and writes target files without reparsing the .cradle scenario.
Backend executables follow this naming convention:
cxc-backend-<name>
For example, cxc-backend-example is selected with --target example.
Understand the boundary
CradleXC handles parsing, validation and backend-neutral resolution. Backend code receives the resulting Deployment IR and translates it into the files required by one target.
Before implementing a target translator, read Hello World: Deployment IR. It contains a complete generated IR example and explains every structure a backend consumes.
Start with an IR fixture
Capture Deployment IR from a small, known scenario:
cxc dump-ir -i scenarios/HelloWorld.cradle > fixture.ir.json
Keep this file with the backend's tests. It provides a stable compiler output that can be used to develop and verify translation logic independently from the scenario parser.
Add more fixtures as backend support grows. Useful cases include:
- multiple networks
- DHCP and static endpoints
- Linux and Windows instances
- routers and computed routes
- local and remote objects
- ordered events with dependencies
- roles and resolved role variables
Translate the IR, not the source
Backend code should not parse .cradle files. CradleXC has already validated
and resolved the scenario into a deployment-ready structure.
At a minimum, translation logic normally reads:
| IR structure | Typical backend use |
|---|---|
project_name | Name the target project and generated resources. |
instances | Define machines, operating systems, resources and provisioning requirements. |
networks | Define subnets, gateways and target-side network resources. |
interfaces | Attach each machine to its resolved networks and addresses. |
routes | Configure static routing where required. |
objects | Locate artifacts required by instances and events. |
| event arrays | Produce ordered event-execution definitions. |
boot_order | Preserve router-first machine ordering. |
Use resolved values exactly as supplied. In particular, do not allocate new
addresses for endpoints that were written as DHCP in source, and do not
recalculate router or Windows status from configuration names.
Map the Hello World IR into a target plan
Start by deciding what files your testbed tooling needs. A small backend can
translate the supplied HelloWorld IR into this target-neutral layout:
output/
├── project.json
├── networks.json
├── instances.json
├── events.json
├── artifacts.json
└── boot-order.txt
The example IR maps into those files as follows:
| IR value | Translation decision |
|---|---|
project_name: "HelloWorld" | Use it as the target project name. |
Network lan_0 | Create one network plan for 192.168.56.0/24 with gateway 192.168.56.1. |
Endpoint win7, 192.168.56.121 | Attach win7 to lan_0 at that exact address. |
Endpoint router, 192.168.56.122 | Attach router to lan_0 at that exact address. |
| Instance resources | Request the supplied CPU and memory values from the target. |
Instance configs | Map each identifier to a target-supported image step, role or setup action. Reject an identifier that the backend cannot implement. |
Object HelloWorld | Make the resolved remote artifact available to the router instance using ttp and filename as destination components. |
main_events | Preserve the supplied order and execution metadata. Do not invent an action when both subject and run_object are null. |
boot_order: ["router", "win7"] | Start the router before the Windows instance. |
Empty arrays and null values are meaningful. An empty routes array means
that the backend must not invent extra static routes. Empty lifecycle phases
mean that there is nothing to schedule in those phases.
Adapt the writers to your testbed
The example separates CRADLE semantics from target syntax. Keep the validation and indexing logic, then replace each output with the equivalent accepted by your environment:
| Generated model | Replace with your target's format |
|---|---|
networks.json | Network definitions, virtual switches, subnets or VLAN configuration. |
instances.json | Machine definitions, image identifiers, resource sizes, interface attachments and setup steps. |
events.json | Scheduled jobs, playbooks or testbed event instructions. |
artifacts.json | Artifact download/copy instructions and destination paths. |
boot-order.txt | Dependency edges or ordered startup instructions. |
Keep provider credentials and site-specific identifiers outside generated files where possible. Accept them through the backend's own configuration, resolve them at runtime, and never copy secrets into logs or fixtures.
After generation, the user reviews the files and applies them with the tools approved for their own testbed. CradleXC supplies the resolved plan; the backend owns the target syntax; the testbed tooling owns infrastructure and runtime execution.
Connect the translator to CradleXC
Keep the translator independent from the executable integration layer. The
integration layer for cxc-backend-<name> should be thin:
- receive the IR file and requested output directory from CradleXC
- decode backend-specific settings into a typed configuration
- call
load_ir()and the target-specific equivalent oftranslate() - write only beneath the requested output directory
- return success only after every required file has been written
- report unsupported IR values and target capabilities as clear errors
Use the backend API from the matching CradleXC release for this integration
layer rather than duplicating its process-handling logic. Keep all target
decisions inside the translator so it can still be run and tested directly
with a captured dump-ir fixture.
Name the installed executable after the target. For a target named lab, the
executable is:
cxc-backend-lab
Place it on PATH or in ~/.cxc/plugins/, make it executable, and verify that
CradleXC discovers it before testing file generation.
Handle IR compatibility
The top-level ir_version identifies the Deployment IR contract. Backend code
should:
- read and validate the version before translating the rest of the document
- reject versions it cannot safely understand with an actionable error
- accept unknown object keys so additive fields do not break older code
- avoid depending on JSON key order or whitespace
- keep fixtures for every supported IR version
Do not compare raw JSON text. Deserialize it into typed structures or access fields by name.
Generate deterministic output
Given the same IR and backend configuration, target-file generation should produce the same result. Deterministic output makes reviews, tests and troubleshooting much easier.
Recommended practices include:
- sort generated maps when the target format does not preserve order
- preserve IR ordering where it carries meaning, especially events and
boot_order - use
provider_namefor target-side network identifiers - keep backend-specific image and provider mappings in backend code
- avoid embedding machine-local temporary paths in generated files
- fail clearly when a required target capability is unsupported
Resolve objects safely
Instance and event structures refer to objects by id. Resolve those IDs
against the top-level objects array.
Object sources use one of these shapes:
{"Remote": "https://172.18.178.10:4443/TTP/HelloWorld/artifact/HelloWorld.sh"}
{"Local": "/absolute/path/file.sh"}
Treat ttp and filename as resolved destination components. Do not parse the
original repository placeholder from CRADLE source again.
Verify discovery and generation
Once the backend package has been installed, confirm that CradleXC can discover it:
cxc plugin list
Then test the public target-file workflow:
cxc build \
-i scenarios/HelloWorld.cradle \
--target example \
-o ./output
Inspect the generated directory and compare it with the expected golden files. Repeat the check with each supported IR fixture and target configuration.
Backend checklist
Before publishing a backend:
- document the supported CradleXC and IR versions
- document supported operating systems, configurations and scenario features
- validate required external dependencies early
- test local and remote artifact handling
- test mixed static and DHCP-originated endpoints
- preserve lifecycle event and boot ordering
- reject unsupported features with clear diagnostics
- keep target-specific state and generated files inside the requested output location
- test failure paths as well as successful generation
- publish compatibility notes alongside each backend release
Use CradleXC's Deployment IR types and backend API as the implementation authority. Keep the public backend documentation focused on supported inputs, generated outputs and target compatibility.